Agent skill

Ffi Capsule Protocol

by apache in apache/datafusion-python

TRIGGER — read before adding, changing, or reviewing any datafusion capsule getter, any FFI export that asks for a TaskContextProvider or an extension codec, or any code that calls…

Apache-2.0Auto-check passedData & Analytics

Install Ffi Capsule Protocol

skills CLI
$ npx skills add apache/datafusion-python --skill ffi-capsule-protocol -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install apache/datafusion-python ffi-capsule-protocol --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/apache/datafusion-python.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.ai/skills/ffi-capsule-protocol .claude/skills/ffi-capsule-protocol && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
ffi-capsule-protocol
GitHub stars
606
Token cost
~3.2k tokens
SKILL.md length
1,470 words
Files
1
Skills in repo
5
Repo updated
First seen
Licence
Apache-2.0

At a glance

TRIGGER — read before adding, changing, or reviewing any datafusion capsule getter, any FFI export that asks for a TaskContextProvider or an extension codec, or any code that calls…

  • Works in 2 steps: It is the wrong registry. Decode… → It dangles. FFI_TaskContextProvider…
  • — read before adding
  • SKILL.md covers Rule 1 — enumerate the family…, Rule 2 — a getter takes the…, Rule 3 — never construct a… and Rule 4 — the helpers live in…, plus 4 more sections
  • Instructions only: no scripts, shell commands, URLs or credentials in SKILL.md

What it does

Ffi Capsule Protocol is an agent skill from apache/datafusion-python. TRIGGER — read before adding, changing, or reviewing any datafusion capsule getter, any FFI export that asks for a TaskContextProvider or an extension codec, or any code that calls FFIQueryPlanner::new / FFITableProvider::new / FFI{Logical,Physical}ExtensionCodec::new. These methods are one protocol with a settled convention. Do not design it fresh; do not construct a SessionContext inside an extension library.

Its SKILL.md is about 3.2k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.

It sits in Data & Analytics. The repository describes itself as: Apache DataFusion Python Bindings. The licence is Apache-2.0.

When your agent uses it

  • — read before adding
  • Reviewing any datafusion capsule getter
  • Any FFI export that asks for a TaskContextProvider
  • An extension codec

Example prompts

  • “/ffi-capsule-protocol”

Workflow steps

2 steps, taken from the first numbered list in SKILL.md.

  1. It is the wrong registry. Decode callbacks resolve names against
  2. It dangles. FFI_TaskContextProvider downgrades its provider to a

What it can do on your machine

Read from SKILL.md and the folder at commit 6c5d9ff. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    No scripts in the folder and no shell commands in SKILL.md (its code samples are bash and rust).

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    Links to these hosts (documentation or services it may open):

    • apache.org
    • github.com

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Ffi Capsule Protocol loads about 3.2k tokens when it runs. Until then it costs about 112 tokens; SKILL.md has 1,470 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~112
When it runs · the whole SKILL.md, loaded when a task matches
~3.2k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from apache/datafusion-python at commit 6c5d9ff, republished under its Apache-2.0 licence (© apache). 1,470 words, ~3,215 tokens.

Download SKILL.mdSave it as .claude/skills/ffi-capsule-protocol/SKILL.md (or your agent's skills folder).
name
ffi-capsule-protocol
description
TRIGGER — read before adding, changing, or reviewing any __datafusion_*__ capsule getter, any FFI_* export that asks for a TaskContextProvider or an extension codec, or any code that calls FFI_QueryPlanner::new / FFI_TableProvider::new / FFI_{Logical,Physical}ExtensionCodec::new. These methods are one protocol with a settled convention. Do not design it fresh; do not construct a SessionContext inside an extension library.
argument-hint
[getter name] (e.g., "__datafusion_query_planner__", "table provider", "codec", or omit to review the whole family)
<!---
  Licensed to the Apache Software Foundation (ASF) under one
  or more contributor license agreements.  See the NOTICE file
  distributed with this work for additional information
  regarding copyright ownership.  The ASF licenses this file
  to you under the Apache License, Version 2.0 (the
  "License"); you may not use this file except in compliance
  with the License.  You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing,
  software distributed under the License is distributed on an
  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  KIND, either express or implied.  See the License for the
  specific language governing permissions and limitations
  under the License.
-->

FFI Capsule Protocol

datafusion-python shares Rust objects with extension libraries through PyCapsules. Every hook is a dunder method named __datafusion_<thing>__ that returns a capsule wrapping an FFI-safe struct. They are one protocol, not a collection of unrelated methods, and they have a settled convention that has already been migrated once (see docs/source/user-guide/upgrade-guides.md, DataFusion 52.0.0 and 55.0.0).

Rule 1 — enumerate the family before you change a member

Do this first, every time. It takes one command and it is the whole point of this skill:

bash
grep -rn "__datafusion_[a-z_]*__" --include="*.rs" crates/ examples/*/src/

Compare the signature you are about to write against what the others already do. If yours is shaped differently, that is a finding about your design, not about theirs.

Rule 2 — a getter takes the session it is being installed on

rust
fn __datafusion_physical_extension_codec__<'py>(
    &self,
    py: Python<'py>,
    session: Bound<'py, PyAny>,
) -> PyResult<Bound<'py, PyCapsule>> { ... }

The host calls the getter and passes itself. That argument is how an extension library reaches things only the session has.

SessionContext implements the same getters and ignores the argument, so a session satisfies the protocol too — ctx.__datafusion_query_planner__() and ctx.__datafusion_query_planner__(ctx) are both valid.

__datafusion_session_planner__(ctx, fallback) is the exception to the shape above: it takes a second argument, the planner assembled so far. A session has one planner slot, so planners compose by nesting rather than by chaining, and the host hands each bundle the previous layer instead of letting it capture one. Wrap fallback and delegate to it; returning a planner that ignores it discards every layer beneath, including one the session already had. It runs after every bundle's codecs are installed, so ctx carries the final chains.

That is also the only hook where it does. __datafusion_session_components__ runs before anything is installed, so its ctx still carries the chains the receiver had — the same session, and the same task-context provider, but not this call's codecs, not even your own. Read the host's codec chains in the planner hook, never in the extension hook.

A codec must always be handed over as an object implementing its getter, never as the bare capsule the getter returns; with_extensions refuses a capsule. A codec's wire id — the string a payload names on decode, which has to mean the same thing in whichever process decodes — is read off the object it arrives as, and a capsule has no type to read one from. Deriving the id from the bundle that contributed the capsule is not the fix: the bundle is whatever object the caller passed, so an application packaging your library inside a bundle of its own would re-tag your payloads and they would stop decoding where they are read. If the object's class name is not the identity you want on the wire, declare __datafusion_codec_id__ on it. BundledLogicalCodec in examples/datafusion-ffi-query-planner-example/src/extension.rs is the shape. This applies only to codecs — a query planner carries no wire id.

Rule 3 — never construct a SessionContext in an extension library

The FFI constructors ask for things a library does not have:

ConstructorWantsTake it from
FFI_{Logical,Physical}ExtensionCodec::newTaskContextProviderffi_task_context_provider_from_pycapsule(&session)
FFI_TableProvider::new_with_ffi_codeclogical codecffi_logical_codec_from_pycapsule(session, None)
FFI_QueryPlanner::new_with_ffi_codecsboth codecsffi_{logical,physical}_codec_from_pycapsule(session, None)

Arc::new(SessionContext::new()) is the wrong answer to all three, for two independent reasons:

  1. It is the wrong registry. Decode callbacks resolve names against whatever provider the codec carries. An empty session resolves nothing, so a function the host registered with register_udf is invisible to a node that references it by name.
  2. It dangles. FFI_TaskContextProvider downgrades its provider to a Weak. A context built inline in the getter is dropped before the capsule is ever used, and every callback then fails with TaskContextProvider went out of scope over FFI boundary.

Prefer the *_with_ffi_codec(s) constructors when they exist. They take prebuilt codecs that already carry the host's provider, so there is no provider parameter to get wrong.

Rule 4 — the helpers live in crates/util/src/lib.rs

ffi_logical_codec_from_pycapsule, ffi_physical_codec_from_pycapsule, ffi_query_planner_from_pycapsule, ffi_task_context_provider_from_pycapsule, table_provider_from_pycapsule. Each takes the object and, where relevant, an Option<&Bound<PyAny>> session:

  • Some(session) — importing a foreign object; the getter needs the session.
  • None — the object already is a session and is being asked for what it holds.

Adding a getter means adding a helper here, not hand-rolling capsule extraction at the call site.

Rule 5 — changing a getter's signature is a breaking change

Extension libraries implement these methods. A signature change breaks every one of them, and the failure is a bare TypeError from a call1. So:

  • Add a section to docs/source/user-guide/upgrade-guides.md with before/after Rust, matching the 52.0.0 and 55.0.0 entries.
  • Add the api change label to the PR.
  • Map the TypeError to a diagnosable message. call_capsule_getter in crates/util/src/lib.rs already does this; reuse it.
  • Update python/datafusion/context.py and python/datafusion/user_defined.py, where the Protocol type hints for these methods live.

Changing what a codec puts on the wire is equally breaking, and easier to miss because no signature moves and nothing fails to compile. Serialized plans outlive the process that wrote them, so the same checklist applies: upgrade guide, api change label, and a statement of exactly which sessions produce different bytes.

Show full SKILL.md (659 more words)Show less

Rule 6 — a session keeps one Arc<SessionContext> for life

FFI_TaskContextProvider holds its provider weakly, and every codec handed to a foreign object carries one. A registered catalog provider upgrades that handle on every supports_filters_pushdown and every scan. The handle is bound to an Arc<SessionContext> allocation, so anything that replaces the allocation orphans every handle bound to the old one: TaskContextProvider went out of scope over FFI boundary.

So mutate SessionState in place — *self.ctx.state_ref().write() = ..., the way add_physical_optimizer_rule and set_session_query_planner both do — rather than deriving a replacement SessionContext. Carry the session id across the rewrite; SessionStateBuilder::new_from_existing drops it and build mints a fresh one, which desyncs session_id() from every TaskContext the session hands out.

Do not try to repair it after the fact:

  • You cannot rebind what you cannot reach. A codec embedded in a registered FFI_CatalogProvider, and in every FFI_SchemaProvider and FFI_TableProvider minted from it, has no Python-side handle.
  • A codec must not retain its session. Codecs are routinely handed to a provider that is registered straight back into the session that built them, closing SessionContext -> catalog -> FFI provider -> FFI codec -> SessionContext.

test_registered_providers_survive_a_planner_install in examples/datafusion-ffi-query-planner-example/python/tests/_test_three_library_query_planner.py guards this. Its WHERE clause is load-bearing: filter pushdown upgrades the weak handle during logical optimization, before plan serialization could fail first for an unrelated reason.

SessionContext.with_extensions is where this rule is easiest to get wrong, because "bind the components to the context you are about to return" reads like an instruction to derive one first. It is not: the factories are handed the receiver, and the returned handle shares its allocation. There is nothing to keep alive separately and nothing to garbage-collect out from under a provider.

SessionContext.enable_url_table is the one method that mints a second allocation for a session. Its result must not outlive the receiver, and it also forks the session's SessionState while keeping its id, so two handles report one session_id() with divergent configuration. That is a bug rather than a design — tracked in apache/datafusion-python#1708 — so do not cite it as precedent for deriving a replacement context.

Rule 7 — installing a planner mutates the session, and says so

set_query_planner returns None, matching add_physical_optimizer_rule. The query planner lives in SessionState, so it belongs to the session and not to a handle on it; every context sharing that session plans through it. Do not reintroduce a with_query_planner that pretends otherwise — the only way to give a handle its own planner is a fresh Arc<SessionContext>, which is what Rule 6 forbids.

Installing a codec rebuilds the installed planner against it, and that rebuild reaches exactly one layer. FFI_QueryPlanner::new_with_ffi_codecs unwraps one ForeignQueryPlanner; a fallback that planner resolved at install time sits in its library's private data with no handle on this side, and cannot re-derive codecs itself because FFI_QueryPlanner holds them by value and Session exposes no accessor for the host's current ones. So do not promise that install order is free — for a layered planner it is not. The examples cannot show this: their fallback lives in the same cdylib as its wrapper, and datafusion-ffi short-circuits a same-library hop rather than serializing. A fix has to come from upstream; tracked in apache/datafusion#24762.

The session's planner also tracks whichever handle wrote it last, so re-installing a planner on the original handle rebinds the session back to that handle's codecs. test_reinstalling_a_planner_rebinds_the_session_to_that_handles_codecs pins that; changing it should be deliberate.

Where the truth is

  • docs/source/extension-guide/ — the protocol, for the library author. capsule-protocol.md has the hook convention and what the getter argument actually is; codecs.md, bundles.md, and query-planners.md have the per-component rules; index.md lists all 18 hooks.
  • docs/source/contributor-guide/ffi-internals.md — why the framing is shaped this way, including the weak-Arc scheme and the one-level rebind.
  • docs/source/user-guide/upgrade-guides.md — every past migration.
  • crates/core/src/codec.rs — the codec chain: the envelope, identity dispatch, and the two unframed cases from Rule 8.
  • examples/datafusion-ffi-example/src/ — provider, catalog, function, codec getters, all in current form. name_only_codec.rs is the codec that encodes nothing.
  • examples/datafusion-ffi-query-planner-example/src/planner.rs — planner getter.
  • examples/datafusion-ffi-query-planner-example/python/tests/_test_three_library_query_planner.py — require_udf_on_decode proves which session a decode callback resolves against. Extend these when touching the protocol.

© apache, Apache-2.0. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

Just SKILL.md in .ai/skills/ffi-capsule-protocol of apache/datafusion-python.

Open the folder on GitHubat commit 6c5d9ff

Compare with similar skills

Ffi Capsule Protocol next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Ffi Capsule Protocol compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Ffi Capsule Protocol this skillapache/datafusion-python606—~3.2kAutomated safety check: PassApache-2.0
Exploratory Data Analysisspacering-net/codeg3.8k15 repos~3.6kAutomated safety check: PassMIT
MatplotlibzLanqing/codex-claude-academic-skills4.6k18 repos~2.9kAutomated safety check: PassMIT
Scikit LearnzLanqing/codex-claude-academic-skills4.6k17 repos~3.9kAutomated safety check: PassBSD-3-Clause
Chart Visualizationbytedance/deer-flow83k2 repos~840Automated safety check: PassMIT
TimesFM Forecastinggoogle-research/timesfm34k—~4.7kAutomated safety check: PassApache-2.0

Similar skills

  • Exploratory Data Analysis

    spacering-net/codeg

    Perform comprehensive exploratory data analysis on scientific data files across 200+ file formats.

    3.8k GitHub starsUsed in 15 repos~3.6k tokens
    Data & AnalyticsAuto-check passed
  • Matplotlib

    zLanqing/codex-claude-academic-skills

    Low-level plotting library for full customization. An agent skill from zLanqing/codex-claude-academic-skills.

    4.6k GitHub starsUsed in 18 repos~2.9k tokens
    Data & AnalyticsAuto-check passed
  • Scikit Learn

    zLanqing/codex-claude-academic-skills

    Machine learning in Python with scikit-learn. An agent skill from zLanqing/codex-claude-academic-skills.

    4.6k GitHub starsUsed in 17 repos~3.9k tokens
    Data & AnalyticsAuto-check passed
  • Chart Visualization

    bytedance/deer-flow

    Picks a suitable chart type from 26 options for your data, maps the data to that chart's parameters and generates a chart image through a JavaScript script.

    83k GitHub starsUsed in 2 repos~840 tokens
    Data & AnalyticsAuto-check passed
  • TimesFM Forecasting

    google-research/timesfm

    Forecasts any univariate time series zero-shot with Google's TimesFM model, returning point forecasts and calibrated prediction intervals without training.

    34k GitHub stars~4.7k tokensUpdated 8 days ago
    Data & AnalyticsAuto-check passed
  • Sandbox Bench

    vercel/next.js

    Official

    Benchmark React or Next.js changes on Vercel Sandbox VMs with paired A/B statistics: react PR/commit vs base, or Next.js PR/commit vs base, measured end-to-end through the bench/render-pipeline app…

    143k GitHub stars~4.1k tokensUpdated today
    Data & AnalyticsAuto-check passed

More from apache/datafusion-python

  • Check Upstream

    apache/datafusion-python

    Check if upstream Apache DataFusion features (functions, DataFrame ops, SessionContext methods, FFI types) are exposed in this Python project.

    606 GitHub stars~5.9k tokensUpdated today
    Auto-check passed
  • Datafusion Python

    apache/datafusion-python

    A skill your agent uses when the user is writing datafusion-python (Apache DataFusion Python bindings) DataFrame or SQL code.

    606 GitHub stars~7.8k tokensUpdated today
    Auto-check passed
  • Audit Skill Md

    apache/datafusion-python

    Audit the user-facing skill at skills/datafusionpython/SKILL.md against the current public Python API.

    606 GitHub stars~3.3k tokensUpdated today
    Auto-check passed
  • Make Pythonic

    apache/datafusion-python

    Audit and improve datafusion-python functions to accept native Python types (int, float, str, bool) instead of requiring explicit lit() or col() wrapping.

    606 GitHub stars~5.8k tokensUpdated today
    Auto-check passed

Questions about Ffi Capsule Protocol

What does Ffi Capsule Protocol do?

TRIGGER — read before adding, changing, or reviewing any datafusion capsule getter, any FFI export that asks for a TaskContextProvider or an extension codec, or any code that calls…. Ffi Capsule Protocol is an agent skill from apache/datafusion-python. TRIGGER — read before adding, changing, or reviewing any datafusion capsule getter, any FFI export that asks for a TaskContextProvider or an extension codec, or any code that calls FFIQueryPlanner::new / FFITableProvider::new / FFI{Logical,Physical}ExtensionCodec::new.

When should I use Ffi Capsule Protocol?

Ffi Capsule Protocol fits situations like: — read before adding; reviewing any datafusion capsule getter; any FFI export that asks for a TaskContextProvider; an extension codec.

How do I install Ffi Capsule Protocol in Claude Code?

Run `npx skills add apache/datafusion-python --skill ffi-capsule-protocol -a claude-code`. Or copy the skill folder (.ai/skills/ffi-capsule-protocol in apache/datafusion-python) into .claude/skills/ffi-capsule-protocol in your project. Claude Code loads it when a task matches its description.

How do I install Ffi Capsule Protocol in Codex?

Run `npx skills add apache/datafusion-python --skill ffi-capsule-protocol -a codex`. Or copy the skill folder (.ai/skills/ffi-capsule-protocol in apache/datafusion-python) into .agents/skills/ffi-capsule-protocol in your project. Codex loads it when a task matches its description.

Can I use Ffi Capsule Protocol in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add apache/datafusion-python --skill ffi-capsule-protocol -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/ffi-capsule-protocol, .gemini/skills/ffi-capsule-protocol, .github/skills/ffi-capsule-protocol and .opencode/skills/ffi-capsule-protocol in your project.

What does Ffi Capsule Protocol need to run?

SKILL.md names no scripts, command-line tools or credentials: Ffi Capsule Protocol is instructions for the agent only.

Does Ffi Capsule Protocol access the network?

SKILL.md names 2 domains. As links in the text: apache.org and github.com. This is read from the text; nothing was executed.

Is Ffi Capsule Protocol safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.

What licence does Ffi Capsule Protocol use?

Ffi Capsule Protocol is published under the Apache-2.0 licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Ffi Capsule Protocol use?

About 3.2k tokens (SKILL.md is roughly 13k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.

What are the alternatives to Ffi Capsule Protocol?

Skills that share tags, products or a category with Ffi Capsule Protocol: Exploratory Data Analysis (spacering-net/codeg, 3.8k stars), Matplotlib (zLanqing/codex-claude-academic-skills, 4.6k stars), Scikit Learn (zLanqing/codex-claude-academic-skills, 4.6k stars) and Chart Visualization (bytedance/deer-flow, 83k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Ffi Capsule Protocol?

apache (a GitHub organization) maintains it in apache/datafusion-python, which has 606 GitHub stars. The repository holds 5 skills in this directory. The repository was last updated on October 7, 2026.

Source: apache/datafusion-python on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.