Gram Functions
Gram Functions is a serverless code execution feature that allows users to deploy custom JavaScript/TypeScript or Python code as callable tools within Gram deployments. Functions can be invoked by AI agents during conversations.
Key Server Packages
| Package |
Purpose |
server/internal/functions/ |
Core functions service - deployment, execution, auth |
server/internal/background/activities/ |
Temporal activities for deploying/reaping function runners |
server/design/functions/ |
Goa API design for functions endpoints |
Key Files
Server Implementation (server/internal/functions/)
impl.go - API service, handles signed asset URL requests from runners
deploy.go - Core interfaces: Deployer, ToolCaller, Orchestrator
deploy_fly.go - Fly.io integration via Machines API
manifest.go - Manifest format (ManifestV0) describing exported tools/resources
runtimes.go - Supported runtime definitions (JS, TS, Python)
auth.go - JWT authentication for function runners
queries.sql - SQL queries for fly_apps table management
Background Workers (server/internal/background/)
activities/deploy_function_runners.go - Deploys runners during deployment processing
activities/reap_functions.go - Cleans up old Fly.io apps
activities/provision_functions_access.go - Creates access credentials for runners
functions_reaper.go - Temporal workflow for cleanup (triggered per-project after deployments)
Function Runner OCI Images (functions/)
The functions/ directory contains the source code and build configuration for the OCI images that run user functions on Fly.io. These images are built using melange and apko for reproducible, minimal container images.
Build System
| File |
Purpose |
melange.yaml |
Builds the gram-runner Go binary as an APK package |
images/nodejs22-alpine3.22.yaml |
apko config for Node.js 22 runtime image |
images/python3.12-alpine3.22.yaml |
apko config for Python 3.12 runtime image |
Image Structure
Each runtime image contains:
- Alpine Linux base with minimal packages (
ca-certificates-bundle, su-exec)
- Language runtime (
nodejs or python3)
- The
gram-runner binary (built from cmd/runner/main.go)
Entrypoint behavior:
gram-runner -init -language <lang> - Initializes filesystem, unzips user code
su-exec gram - Drops privileges to non-root gram user (UID 10000)
gram-runner -language <lang> - Starts HTTP server on port 8888
Runner Binary (cmd/runner/main.go)
The gram-runner binary is an HTTP server that:
- Listens on
:8888 for tool call and resource requests
- Authenticates requests using JWT tokens
- Spawns language-specific subprocesses to execute user code
- Communicates with subprocesses via named pipes (FIFO)
- Reports resource usage (CPU, memory, execution time) as HTTP trailers
- Auto-terminates after 1 minute of idle time (scale-to-zero support)
Internal Packages (functions/internal/)
| Package |
Purpose |
auth/ |
JWT authentication and request authorization middleware |
bootstrap/ |
Machine initialization: unzip code, prepare entrypoints, lazy asset loading |
encryption/ |
AES-GCM encryption for secure communication |
guardian/ |
Process execution with resource limits |
ipc/ |
Named pipe (FIFO) creation for subprocess communication |
javascript/ |
JavaScript/TypeScript entrypoint script (gram-start.js) |
python/ |
Python entrypoint script (gram_start.py) |
runner/ |
HTTP handlers for /tool-call and /resource-request endpoints |
svc/ |
Service utilities (idle tracking, secrets, errors) |
o11y/ |
Observability setup (OpenTelemetry, logging) |
middleware/ |
HTTP middleware (recovery, version header) |
attr/ |
Structured logging attribute helpers |
Tool Call Execution Flow
- Request received at
POST /tool-call with JSON payload:{"name": "tool_name", "input": {...}, "environment": {...}}
- FIFO created - Named pipe for IPC with subprocess
- Subprocess spawned -
node --experimental-strip-types gram-start.js or python gram_start.py
- Arguments passed - FIFO path, serialized request, request type ("tool" or "resource")
- Response read - HTTP response format read from FIFO
- Metrics collected - CPU time, memory, execution duration added as trailers
- Cleanup - FIFO removed, subprocess waited
Lazy Asset Loading
For large function bundles (>700KiB), the code isn't embedded in the Fly machine config. Instead:
- A
.lazy file is written containing the asset ID
- On init,
bootstrap.resolveLazyFile() detects the .lazy file
- Runner fetches a pre-signed URL from the Gram server
- Code is downloaded from Tigris blob storage and unzipped
Database Tables
| Table |
Purpose |
deployments_functions |
Links functions to deployments, stores runtime/slug |
functions_access |
Encryption keys and bearer token formats for auth |
fly_apps |
Tracks deployed Fly.io apps (status, region, URL, reap state) |
function_tool_definitions |
Tool metadata (name, description, input schema, variables) |
function_resource_definitions |
Resource metadata (URI, mime type) |
Deployment Flow
- Upload - User uploads ZIP archive with function code +
manifest.json
- Processing - Temporal workflow triggers
DeployFunctionRunners activity
- Fly.io Deployment - Creates Fly app via Machines API with appropriate runtime image
- Asset Handling - Small functions embedded in config; large functions use Tigris blob storage with lazy loading
- Auto-scaling - 2 machines per function, scale to 0 when idle
Execution Flow
- AI agent requests tool call →
FlyRunner.ToolCall() sends authenticated HTTP request
- Runner receives request at
/tool-call endpoint
- Subprocess spawned (
node/python) with user code
- Communication via named pipe (FIFO)
- Response streamed back with resource usage metrics as HTTP trailers
Cleanup (Reaping)
- Functions Reaper workflow is triggered after each deployment completes (see
server/internal/deployments/impl.go)
- Keeps only N most recent deployments' Fly apps per project (default: 3)
- Old apps deleted via Machines API, marked
reaped_at in database
- Note: A
FunctionsReaperScopeGlobal scope exists in the code but is not currently used/scheduled
1---2name: gram-functions3description: A walkthrough of the Gram Functions feature in this codebase4---56# Gram Functions78Gram Functions is a serverless code execution feature that allows users to deploy custom JavaScript/TypeScript or Python code as callable tools within Gram deployments. Functions can be invoked by AI agents during conversations.910## Key Server Packages1112| Package | Purpose |13| ---------------------------------------- | ---------------------------------------------------------- |14| `server/internal/functions/` | Core functions service - deployment, execution, auth |15| `server/internal/background/activities/` | Temporal activities for deploying/reaping function runners |16| `server/design/functions/` | Goa API design for functions endpoints |1718## Key Files1920### Server Implementation (`server/internal/functions/`)2122- **`impl.go`** - API service, handles signed asset URL requests from runners23- **`deploy.go`** - Core interfaces: `Deployer`, `ToolCaller`, `Orchestrator`24- **`deploy_fly.go`** - Fly.io integration via Machines API25- **`manifest.go`** - Manifest format (`ManifestV0`) describing exported tools/resources26- **`runtimes.go`** - Supported runtime definitions (JS, TS, Python)27- **`auth.go`** - JWT authentication for function runners28- **`queries.sql`** - SQL queries for `fly_apps` table management2930### Background Workers (`server/internal/background/`)3132- **`activities/deploy_function_runners.go`** - Deploys runners during deployment processing33- **`activities/reap_functions.go`** - Cleans up old Fly.io apps34- **`activities/provision_functions_access.go`** - Creates access credentials for runners35- **`functions_reaper.go`** - Temporal workflow for cleanup (triggered per-project after deployments)3637## Function Runner OCI Images (`functions/`)3839The `functions/` directory contains the source code and build configuration for the OCI images that run user functions on Fly.io. These images are built using [melange](https://github.com/chainguard-dev/melange) and [apko](https://github.com/chainguard-dev/apko) for reproducible, minimal container images.4041### Build System4243| File | Purpose |44| ----------------------------------- | ---------------------------------------------------- |45| `melange.yaml` | Builds the `gram-runner` Go binary as an APK package |46| `images/nodejs22-alpine3.22.yaml` | apko config for Node.js 22 runtime image |47| `images/python3.12-alpine3.22.yaml` | apko config for Python 3.12 runtime image |4849### Image Structure5051Each runtime image contains:5253- Alpine Linux base with minimal packages (`ca-certificates-bundle`, `su-exec`)54- Language runtime (`nodejs` or `python3`)55- The `gram-runner` binary (built from `cmd/runner/main.go`)5657**Entrypoint behavior:**58591. `gram-runner -init -language <lang>` - Initializes filesystem, unzips user code602. `su-exec gram` - Drops privileges to non-root `gram` user (UID 10000)613. `gram-runner -language <lang>` - Starts HTTP server on port 88886263### Runner Binary (`cmd/runner/main.go`)6465The `gram-runner` binary is an HTTP server that:6667- Listens on `:8888` for tool call and resource requests68- Authenticates requests using JWT tokens69- Spawns language-specific subprocesses to execute user code70- Communicates with subprocesses via named pipes (FIFO)71- Reports resource usage (CPU, memory, execution time) as HTTP trailers72- Auto-terminates after 1 minute of idle time (scale-to-zero support)7374### Internal Packages (`functions/internal/`)7576| Package | Purpose |77| ------------- | --------------------------------------------------------------------------- |78| `auth/` | JWT authentication and request authorization middleware |79| `bootstrap/` | Machine initialization: unzip code, prepare entrypoints, lazy asset loading |80| `encryption/` | AES-GCM encryption for secure communication |81| `guardian/` | Process execution with resource limits |82| `ipc/` | Named pipe (FIFO) creation for subprocess communication |83| `javascript/` | JavaScript/TypeScript entrypoint script (`gram-start.js`) |84| `python/` | Python entrypoint script (`gram_start.py`) |85| `runner/` | HTTP handlers for `/tool-call` and `/resource-request` endpoints |86| `svc/` | Service utilities (idle tracking, secrets, errors) |87| `o11y/` | Observability setup (OpenTelemetry, logging) |88| `middleware/` | HTTP middleware (recovery, version header) |89| `attr/` | Structured logging attribute helpers |9091### Tool Call Execution Flow92931. **Request received** at `POST /tool-call` with JSON payload:94 ```json95 {"name": "tool_name", "input": {...}, "environment": {...}}96 ```972. **FIFO created** - Named pipe for IPC with subprocess983. **Subprocess spawned** - `node --experimental-strip-types gram-start.js` or `python gram_start.py`994. **Arguments passed** - FIFO path, serialized request, request type ("tool" or "resource")1005. **Response read** - HTTP response format read from FIFO1016. **Metrics collected** - CPU time, memory, execution duration added as trailers1027. **Cleanup** - FIFO removed, subprocess waited103104### Lazy Asset Loading105106For large function bundles (>700KiB), the code isn't embedded in the Fly machine config. Instead:1071081. A `.lazy` file is written containing the asset ID1092. On init, `bootstrap.resolveLazyFile()` detects the `.lazy` file1103. Runner fetches a pre-signed URL from the Gram server1114. Code is downloaded from Tigris blob storage and unzipped112113## Database Tables114115| Table | Purpose |116| ------------------------------- | ------------------------------------------------------------- |117| `deployments_functions` | Links functions to deployments, stores runtime/slug |118| `functions_access` | Encryption keys and bearer token formats for auth |119| `fly_apps` | Tracks deployed Fly.io apps (status, region, URL, reap state) |120| `function_tool_definitions` | Tool metadata (name, description, input schema, variables) |121| `function_resource_definitions` | Resource metadata (URI, mime type) |122123## Deployment Flow1241251. **Upload** - User uploads ZIP archive with function code + `manifest.json`1262. **Processing** - Temporal workflow triggers `DeployFunctionRunners` activity1273. **Fly.io Deployment** - Creates Fly app via Machines API with appropriate runtime image1284. **Asset Handling** - Small functions embedded in config; large functions use Tigris blob storage with lazy loading1295. **Auto-scaling** - 2 machines per function, scale to 0 when idle130131## Execution Flow1321331. AI agent requests tool call → `FlyRunner.ToolCall()` sends authenticated HTTP request1342. Runner receives request at `/tool-call` endpoint1353. Subprocess spawned (`node`/`python`) with user code1364. Communication via named pipe (FIFO)1375. Response streamed back with resource usage metrics as HTTP trailers138139## Cleanup (Reaping)140141- Functions Reaper workflow is triggered after each deployment completes (see `server/internal/deployments/impl.go`)142- Keeps only N most recent deployments' Fly apps per project (default: 3)143- Old apps deleted via Machines API, marked `reaped_at` in database144- Note: A `FunctionsReaperScopeGlobal` scope exists in the code but is not currently used/scheduled