​
Overview

A Chalk Function is a Python callable that you deploy onto a Scaling Group and register in the Chalk Catalog. Decorate a function with @chalkcompute.function and it becomes reachable from anywhere Chalk runs: local scripts and notebooks, other functions, and (because of the Catalog registration) the feature engine, including Chalk SQL, resolvers, the query server, and DataFrames via F.catalog_call(). Arguments and results are serialized automatically; you write ordinary Python and Chalk handles scheduling, scaling, and delivery.

On top of what a bare Scaling Group provides, functions add async / streaming invocation, nested calls, automatic retries, concurrency caps, rate limits, and request-queue-driven autoscaling. Reach for a function when you want any of those features, or when you want the callable to be visible to the rest of Chalk.

Functions support three return styles:

  • Single value: return a result and the caller receives it.
  • Async generator: yield results incrementally and the caller receives them as they’re produced. See Streaming results for an end-to-end example.
  • Nested calls: a function can call other functions, composing pipelines without manual orchestration. See Nested function calls for a pipeline example.

To define and deploy a function, see Defining a function.

For jobs with multiple steps and separate task environments, see Workflows.


​
How it works

@chalkcompute.function is backed by Chalk Compute’s async execution layer. At a high level, calling a function goes through three moving parts:

┌──────────┐         ┌──────────────────┐         ┌───────────┐
│  Caller  │──call──▸│  Function Queue  │──run───▸│  Worker   │
│          │◂results─│                  │◂results─│ (Python)  │
└──────────┘         └──────────────────┘         └───────────┘
  • The caller (local script, notebook, another function) submits an invocation and receives results over a single bidirectional stream.
  • The Function Queue durably accepts the work, assigns each call an ID, enforces configured policies (concurrency, rate limit, retry), and relays results back to the caller.
  • The worker is the Python process that actually runs your function code.

Callers and workers are loosely coupled: the caller never opens a connection directly to a worker. Workers can scale up, scale down, or get rescheduled mid-flight and in-flight calls are not lost. This is what enables streaming async generators, nested calls, and policy-enforced dispatch.

​
Policy enforcement

The queue enforces the policies declared on each function (rate limit, concurrency, and retries) before dispatching work to the worker.

PolicyWhat it caps
rate_limitOutbound requests per second / minute / hour, optionally shared across functions via a key.
concurrencyIn-flight invocations at once, optionally shared across functions via a key. Defaults to 1 per function.
retriesAutomatic retry of transient failures (UNAVAILABLE, DEADLINE_EXCEEDED, RESOURCE_EXHAUSTED, ABORTED) with exponential backoff and jitter. Other errors are terminal.

Concurrency is acquired before dispatching, so a burst of queued calls backpressures on the semaphore rather than spawning unbounded tasks. Rate-limit tokens are consumed per attempt: each retry takes a fresh token.

​
Retry safety

Retries are safe for failures that happen before the function has started streaming results: connection errors, DEADLINE_EXCEEDED on the initial call, etc. If a function has already yielded chunks when it fails, those chunks are visible to the caller and a retry would produce an ambiguous stream. Retries therefore apply cleanly only to pre-stream failures today; making mid-stream retries safe is a known follow-up.