CircleCI Functions swap orb shell scripts for compiled steps an agent can check before pushing
Priya Nair
The last time I let a coding agent wire up CI on a side project, it skipped the perfectly good setup step sitting in the registry and wrote its own Node install script. The script worked on the third push. I spent the first two watching red builds and wondering why I'd delegated this at all.
So I read CircleCI's announcement of Functions with a little more personal interest than usual. CircleCI says that in its testing, agents without Functions bypassed the available orbs and generated their own setup scripts about half the time. That's my side project, measured.
The short version of the release
Functions are reusable CI steps that ship as prebuilt, tested code with clearly defined inputs. They're in beta, free during the beta, and available on every CircleCI plan, according to the announcement. You declare them in a new functions: block and call them by name inside a job:
version: 2.1
functions:
setup-node: github.com/circleci-functions/setup-node@v0.4.0
jobs:
test:
docker:
- image: cimg/base:current
steps:
- checkout
- setup-node:
with:
node-version: "22"
- run: npm test
workflows:
main:
jobs:
- test
The beta catalog has ten CircleCI-maintained Functions: setup-node, setup-python, setup-go, setup-ruby, setup-java, setup-dotnet, setup-aws, setup-docker, setup-browser-tools and setup-github-cli. Writing your own Functions, and step outputs, are listed as planned for later releases.
Where an orb command and a Function part ways
Orbs aren't going anywhere. CircleCI says the two coexist, no migration is required and no deprecation of orbs is planned. The difference is in what runs when the step executes.
An orb command typically runs a shell script. CircleCI's point is that an agent, looking at that command, often can't tell whether the setup step works until a pipeline actually runs it. A Function runs a compiled binary, built for Linux, macOS and Windows, behind a declared set of inputs. If you pass an input it doesn't accept, you get an error that names the problem and lists the inputs it does accept.
The language setup Functions also cache dependencies by default, and CircleCI says that cache persists even when a job fails, times out or is canceled. That one I'll feel on a flaky test suite, where the retry currently pays the full install again. You can switch it off per step with cache: false under with:.
Versions are exact and immutable. Aliases like @latest or @v1 aren't allowed.
How an agent (or you) finds and checks them
Discovery goes through the CircleCI CLI, version 1.8.0 or later, and its built-in MCP server. The CLI side is four commands:
circleci function list # browse what exists
circleci function get setup-node # inspect its inputs
circleci function add setup-node # add it to your config
circleci config validate # check the config before pushing
For agent tooling, the CLI can start its MCP server (circleci mcp start), and the announcement includes enable commands for Cursor, VS Code and Claude Desktop. The whole release is built around that loop. The agent looks up the step, reads its inputs, writes the config and validates it locally, before a single runner minute is spent.
CircleCI's own numbers from internal testing are that agents set up working pipelines up to twice as fast and used about 40% fewer tokens with Functions, and that a Node setup step compiled to roughly seven times less config than the equivalent orb command. Those are vendor benchmarks, so measure your own pipelines before you quote them.
The rough edges I'd plan for
It's a beta with ten Functions, all written by CircleCI. If your setup step is anything more exotic than a language runtime or a common CLI, you're still on orbs or run: steps until custom Functions arrive.
A compiled binary is also harder to read than a shell script. When an orb command misbehaves, I can open the script and see the curl line that broke. With a Function, I'm trusting CircleCI's tests and whatever the error message tells me. For most teams that's a fair trade for setup steps. It's still a trade.
Exact, immutable versions are good supply-chain hygiene, and they also mean nothing moves until someone bumps the version string. Check whether your update bot understands the new functions: block before you assume it'll open those PRs for you. The announcement doesn't say.
How other CI tools package reusable steps
Typed, versioned, reusable steps aren't new as an idea. What CircleCI is adding is the compiled binary and the agent-facing discovery loop. Here's how the tools I see most often handle the same job.
| Tool | Reusable unit | How inputs are checked |
|---|---|---|
| GitHub Actions | Actions referenced with uses: owner/repo@ref, inputs declared in action.yml |
Unknown inputs produce a warning at run time. Tags are mutable, so GitHub recommends pinning to a full commit SHA (@<full-40-char-sha>). |
| GitLab CI/CD | CI/CD components from the CI/CD Catalog, pulled in with include: component: |
spec: inputs: with types, allowed options and regex, checked when the pipeline is created |
| Azure Pipelines | YAML templates with parameters: |
Typed parameters and allowed values: are checked when the template is expanded, before any job runs |
| Dagger | Modules of Dagger Functions written in Go, Python or TypeScript | Ordinary typed function signatures, and the same call runs on your laptop and in any CI |
| Buddy | Prebuilt actions, each a type: with its own named fields, usable from the UI or YAML |
Fields are configured per action, and the language runtime usually comes from the container image rather than a setup script |
If you're on GitLab and want typed, versioned reusable steps today without a beta label, CI/CD components with spec: inputs: are the better fit than anything in this table. If you want one step definition that runs the same way on a laptop and in two different CI systems, Dagger is.
The reason I'd look at Buddy for this particular problem is that a language setup step mostly disappears. A build action names its image, and the runtime is already there, so there's no install script for an agent to rewrite:
- action: "Run tests"
type: "BUILD"
docker_image_name: "library/node"
docker_image_tag: "<node-version>"
execute_commands:
- "npm ci"
- "npm test"
The flip side is that you pick and maintain the image tag yourself, which is the same bump problem as an exact Function version in a different place.
What I'm watching next
Custom Functions. Ten CircleCI-maintained setup steps make a nice demo. The real test is whether a platform team can publish its own internal steps as Functions, so that the agent on a product team reaches for the company's setup step instead of improvising one. If you try the beta with an agent in the loop, I'd like to know whether that half-the-time number moves for you.
Source: CircleCI (circleci.com)