elfeed-ai manual
Table of contents
Overview
elfeed-ai.el adds AI-powered content curation to elfeed, the Emacs
feed reader. It solves the problem of information overload: rather
than manually scanning every entry in your feeds, you describe your
interests once in natural language, and elfeed-ai continuously
evaluates new entries and surfaces only the content that matters to
you.
The package uses gptel to send each entry’s title and content to a
language model, which returns a relevance score between 0.0 and 1.0
and, optionally, a brief summary. If elfeed-ai-relevance-threshold
is set, entries scoring at or above the threshold are tagged (by
default with elfeed-ai), which you can use in elfeed’s standard
search filter syntax to view only curated content.
elfeed-ai integrates with elfeed through three mechanisms:
Automatic scoring via
elfeed-new-entry-hook. When new entries arrive during an elfeed update, they are queued and scored asynchronously one at a time. Each scored entry receives an AI relevance score and a brief summary, stored aselfeed-metaproperties (Scoring entries).Search buffer column. A custom
elfeed-search-print-entry-functionadds a score column between the date and title, with color-coded faces for high and low scores (Faces).Show buffer summary. After-advice on
elfeed-show-refreshinjects the AI-generated summary above the original content, giving you a quick overview before reading the full entry.
A daily budget system prevents runaway API costs. The budget can
track either estimated tokens or actual dollar cost (the latter
requires gptel-plus), and resets at a configurable hour each day
(Budget).
The package also provides a transient menu for interactive adjustment of all settings (Transient menu).
Dependencies: elfeed (3.4.1+), gptel (0.9+), transient (0.7+),
Emacs 29.1 or later.
Quick start
(require 'elfeed-ai)
(setopt elfeed-ai-interest-profile
"AI safety, Emacs, functional programming, philosophy of mind,
effective altruism, longevity research")
(elfeed-ai-mode 1)
New entries are scored automatically when elfeed fetches them. Scores are stored as entry metadata, so you can sort the search buffer by relevance.
To also filter by relevance, set elfeed-ai-relevance-threshold to a
value between 0.0 and 1.0 (e.g., 0.5). Entries scoring at or above
the threshold are tagged with elfeed-ai-score-tag (default
elfeed-ai), which you can use in elfeed search filters:
+elfeed-ai +unread
You can also point elfeed-ai-interest-profile at a file containing a
longer profile (Interest profile). To score entries that arrived
before you enabled the mode, use M-x elfeed-ai-score-unscored
(Scoring unscored entries).
The development repository is on GitHub.
User options
Interest profile
The user option elfeed-ai-interest-profile is a free-form string
describing your interests in natural language. This string is included
verbatim in every AI scoring prompt, so the more specific and
descriptive it is, the better the model can judge relevance.
The value accepts two forms:
- An inline string (e.g.,
"AI safety, Emacs, functional programming"). - A file path pointing to a readable file whose contents are used as the profile text. This is useful for lengthy, detailed profiles that would be unwieldy as a Lisp string.
When the value is empty, scoring is skipped entirely—no API calls are made. The default value is an empty string.
The type is (choice string file).
Scoring behavior
The user option elfeed-ai-auto-score controls whether new entries are
automatically queued for scoring as they arrive via
elfeed-new-entry-hook. The default is t. Set it to nil if you
prefer to score entries manually with elfeed-ai-score (Scoring
entries).
The user option elfeed-ai-generate-summary controls whether the AI
produces a short summary alongside the relevance score. The default
is t. Summaries are displayed in the show buffer above the original
content. Setting this to nil reduces token usage per entry, which
is useful if you are on a tight budget (Budget) or only care about
the score.
The user option elfeed-ai-summary-prompt is the instruction appended
to the scoring prompt that describes what the summary should contain.
It is only used when elfeed-ai-generate-summary is non-nil. The
text is inserted after the request for a "summary" JSON key, so it
should describe the desired content and length of the summary rather
than repeating structural instructions. The default value is
"a 1-3 sentence summary of the content". You might change this to,
for example, "a one-paragraph explanation of why this is relevant to the user's interests" to get more targeted summaries. The type is
string.
The user option elfeed-ai-relevance-threshold sets the minimum AI
score (a float between 0.0 and 1.0) for an entry to receive the
elfeed-ai-score-tag tag. When nil (the default), no tags are
added and entries are scored only—you can still sort by score and
view summaries. When set to a number, entries scoring at or above the
threshold are tagged, and entries scoring below it are not tagged. In
both cases, the score is stored in elfeed-meta.
The user option elfeed-ai-score-unscored-days specifies the default
number of days to look back when invoking elfeed-ai-score-unscored
(Scoring unscored entries). The default is 7. With a prefix
argument, the command prompts for a custom number of days, ignoring
this option.
The user option elfeed-ai-max-content-length sets the maximum number
of characters of entry content sent to the AI. Longer content is
truncated. The default is 4000. Increasing this value gives the
model more context for judging relevance, at the cost of higher token
usage.
Budget
The user option elfeed-ai-daily-budget caps daily API usage. Its
value is a cons cell of the form (TYPE . LIMIT), where TYPE is
either tokens or dollars.
In tokens mode, usage is estimated heuristically at one token per
four characters. This provides a rough safeguard without requiring
external cost-tracking infrastructure.
In dollars mode, actual cost is computed via gptel-plus-compute-cost
from the gptel-plus package. If gptel-plus is not available, the
budget is not enforced and a warning is logged at startup. Dollar
mode also displays the cost of the last 100 scored entries in the
transient menu heading (Transient menu), helping you calibrate your
budget.
The default value is (dollars . 1.00). The type is
(choice (cons tokens integer) (cons dollars number)).
The obsolete variable elfeed-ai-daily-token-budget is superseded by
elfeed-ai-daily-budget as of version 0.2.0.
The user option elfeed-ai-budget-reset-hour specifies the hour
(0–23) at which the daily budget resets. If the current hour is
before this value, yesterday’s date is used for budget tracking, so a
late-night scoring session counts against the previous day’s budget.
The default is 0 (midnight).
The user option elfeed-ai-budget-file specifies the file used to
persist daily usage data across Emacs sessions. The default is
elfeed-ai-budget.eld in user-emacs-directory.
Tags
The user option elfeed-ai-score-tag is the tag added to entries whose
AI score meets or exceeds elfeed-ai-relevance-threshold
(Scoring behavior). This tag is only used when
elfeed-ai-relevance-threshold is non-nil. The default is
elfeed-ai. You can change this to any symbol, then use it in elfeed
search filters (e.g., +my-custom-tag +unread).
AI backend and model
The user option elfeed-ai-backend specifies the gptel backend name
for AI scoring (e.g., "Gemini" or "Claude"). When nil, the
backend is inferred: if elfeed-ai-model is set, elfeed-ai searches
gptel--known-backends for a backend that provides that model; if no
match is found, it falls back to gptel-backend. The default is
nil.
The user option elfeed-ai-model specifies the gptel model for AI
scoring (e.g., claude-sonnet-4-5-20250514). When nil, defaults to
gptel-model. The default is nil.
Both options can be adjusted interactively via the transient menu (Transient menu). Setting these options allows you to use a different (potentially cheaper or faster) model for feed scoring than your default gptel model.
Display
The user option elfeed-ai-sort-by-score controls whether the search
buffer is sorted by AI score (highest first) when elfeed-ai-mode is
active. Unscored entries are sorted after scored ones; entries with
equal scores are sorted by date (newest first). The default is t.
You can toggle sorting at any time with elfeed-ai-toggle-sort
(Toggling sort order).
The user option elfeed-ai-score-high-threshold sets the minimum
score for the high-score face. Scores at or above this value are
rendered with elfeed-ai-score-high-face (Faces). The default is
0.7.
The user option elfeed-ai-score-low-threshold sets the maximum score
for the low-score face. Scores at or below this value are rendered
with elfeed-ai-score-low-face (Faces). The default is 0.3.
Scores between the two thresholds use the default
elfeed-ai-score-face.
Commands
Enabling the mode
The command elfeed-ai-mode is a global minor mode that activates all
elfeed-ai integrations. When enabled, it:
- Sets
elfeed-search-print-entry-functionto a custom function that displays the AI score column (Search buffer display). - Installs
elfeed-ai-sortaselfeed-search-sort-functionifelfeed-ai-sort-by-scoreis non-nil (Display). - Adds
elfeed-ai--enqueuetoelfeed-new-entry-hookifelfeed-ai-auto-scoreis non-nil (Scoring behavior). - Advises
elfeed-show-refreshto inject AI summaries. - If
elfeed-tubeis available, adviseselfeed-tube-showto re-inject the AI summary after elfeed-tube modifies the show buffer (elfeed-tube integration).
When disabled, all integrations are cleanly removed and the original print and sort functions are restored. The pending scoring queue is also cleared.
Scoring entries
The command elfeed-ai-score is a context-sensitive command for
scoring entries. Its behavior depends on the current buffer:
In the show buffer, it scores the displayed entry. If the entry has already been scored, it signals an error unless called with a prefix argument (
C-u), which forces a re-score. After scoring, it refreshes the show buffer to display the new summary.In the search buffer, it queues all selected entries (or the entry at point when no region is active) for scoring. Already-scored entries are skipped unless called with a prefix argument.
In both cases, the command reports how many entries were queued and how many were already scored. Scoring proceeds asynchronously in the background; a message is displayed in the echo area when the batch completes, along with the total cost if dollar tracking is available.
Scoring unscored entries
The command elfeed-ai-score-unscored walks the entire elfeed
database and queues all entries from the last N days that have not yet
been scored. By default, N is the value of
elfeed-ai-score-unscored-days (Scoring behavior). With a prefix
argument (C-u), the command prompts for a custom number of days.
This is useful when you first enable elfeed-ai on an existing elfeed
database, or after changing your interest profile and wanting to
re-evaluate recent entries.
Checking budget status
The command elfeed-ai-budget-status displays the current daily usage
in the echo area. In tokens mode, it shows tokens used versus the
limit. In dollars mode, it shows the dollar amount used versus the
limit. Both modes show the remaining budget.
Toggling sort order
The command elfeed-ai-toggle-sort switches between sorting the
search buffer by AI score (highest first) and the original sort order
(typically by date). When score sorting is enabled,
elfeed-search-sort-function is set to elfeed-ai-sort
(Sorting); when disabled, the function saved before elfeed-ai
modified it is restored.
The search buffer is refreshed immediately after toggling. This command is also available in the transient menu (Transient menu).
Functions
Scoring
The function elfeed-ai-score-entry is the core scoring function. It
takes an elfeed ENTRY and a CALLBACK, sends the entry to the
configured AI backend via gptel-request, and calls CALLBACK with a
(score . summary) cons cell on success or nil on failure.
Before making the API call, the function checks three preconditions:
the interest profile must be non-empty, the daily budget must not be
exhausted, and the entry must have at least a title or content. If
any check fails, CALLBACK is called with nil immediately.
The function constructs a system message containing the interest
profile and scoring instructions, and a user message containing the
entry’s title, author, and truncated content. The system message is
marked for prompt caching via gptel-use-cache, so that repeated
scoring requests reuse the cached system prompt. Tool use is explicitly
disabled (gptel-use-tools) to ensure the model returns a plain JSON
response rather than attempting tool calls.
On receiving a response, the function parses the JSON, records token
and cost usage (Budget), stores the score and summary as
elfeed-meta properties on the entry, and, if
elfeed-ai-relevance-threshold is non-nil, applies or removes the
relevance tag (Scoring behavior).
The function elfeed-ai-budget-exhausted-p returns non-nil if today’s
budget is exhausted. This is useful in custom hooks or conditions
where you want to check whether scoring will proceed before queuing
entries.
Search buffer display
The function elfeed-ai-search-print-entry is the custom
elfeed-search-print-entry-function installed by elfeed-ai-mode.
It renders each entry with the standard date, title, feed, and tags
columns, plus an additional score column between the date and title.
The score column is 6 characters wide. Scored entries display their
score as a four-character float (e.g., 0.85); unscored entries
display = - =. The score is propertized with one of three faces
depending on the thresholds (Display):
elfeed-ai-score-high-facefor scores at or aboveelfeed-ai-score-high-threshold.elfeed-ai-score-low-facefor scores at or belowelfeed-ai-score-low-threshold.elfeed-ai-score-facefor scores in between, or for unscored entries.
Sorting
The function elfeed-ai-sort is the sort predicate installed as
elfeed-search-sort-function when score sorting is active. It sorts
entries by descending AI score, with unscored entries (which have no
:ai-score metadata) placed after all scored entries. Entries with
equal scores are sorted by date (newest first).
Internally, unscored entries are assigned a sentinel value of -1.0,
which is below the valid score range of 0.0–1.0, ensuring they
always appear last.
Faces
elfeed-ai defines four faces for visual presentation of AI data:
elfeed-ai-score-face— the default face for mid-range AI scores in the search buffer. Inherits fromshadow. Applied to scores betweenelfeed-ai-score-low-thresholdandelfeed-ai-score-high-threshold, and to unscored entries.elfeed-ai-score-high-face— face for high AI scores in the search buffer. Inherits fromsuccesswith bold weight. Applied to scores at or aboveelfeed-ai-score-high-threshold(default0.7).elfeed-ai-score-low-face— face for low AI scores in the search buffer. Inherits fromshadow. Applied to scores at or belowelfeed-ai-score-low-threshold(default0.3).elfeed-ai-summary-heading-face— face for the “AI Summary” heading in the show buffer. Inherits fromfont-lock-keyword-facewith bold weight.
You can customize these faces with M-x customize-face or by setting
them in your init file. The score thresholds that determine which
face is used are controlled by elfeed-ai-score-high-threshold and
elfeed-ai-score-low-threshold (Display).
Transient menu
The command elfeed-ai-menu opens a transient menu providing
centralized access to all elfeed-ai commands and settings. The menu
is organized into four groups:
Score: commands for scoring the current entry or selection (
elfeed-ai-score, bound tos) and for scoring unscored entries (elfeed-ai-score-unscored, bound toS).Display: toggles for sort order (
elfeed-ai-toggle-sort, bound tot) and the minor mode (elfeed-ai-mode, bound tom).Scoring: interactive adjustment of the AI model (
-m), auto-scoring (-a), summary generation (-s), relevance threshold (-r), max content length (-l), and the number of days forelfeed-ai-score-unscored(-d).Budget: toggle between token and dollar budget types (
-t), and set the budget limit (-b). In dollar mode, the group heading displays the total cost of the last 100 scored entries, helping you calibrate the daily limit.
Changes made through the transient menu take effect immediately but are not persisted across sessions. To make them permanent, set the corresponding user options in your init file.
You might bind the menu to a convenient key in elfeed:
(define-key elfeed-search-mode-map (kbd "Y") #'elfeed-ai-menu)
(define-key elfeed-show-mode-map (kbd "Y") #'elfeed-ai-menu)
elfeed-tube integration
If you use elfeed-tube alongside elfeed-ai, the two packages
interact in the show buffer. The issue is that elfeed-tube-show
replaces or modifies the show buffer content after
elfeed-show-refresh has already run, which would normally remove the
injected AI summary.
elfeed-ai handles this automatically. When elfeed-ai-mode is
enabled, it advises elfeed-tube-show so that the AI summary is
re-injected after elfeed-tube finishes its modifications. This advice
is installed eagerly if elfeed-tube is already loaded, or deferred
via with-eval-after-load if it has not yet been loaded.
No configuration is required on your part: if both packages are installed and their respective modes are active, the integration works transparently.