Runtime Configuration
The engine accepts functional options that control which capabilities are available to scripts, how results are encoded, how execution is logged, and how concurrency is managed.
Configuration errors
Engine and session options are applied in order. Ferret ignores nil options, applies every other option, and joins multiple failures into the returned error. Construction returns no engine or session when option application fails, and releases any engine-owned services created by those options.
Simple validation failures can be inspected with
github.com/ziflex/go-options.ValidationError. Applications only need this
import when they want structured error details; ordinary Ferret option calls
continue to use ferret.Option, ferret.SessionOption, and the ferret.With*
functions.
errors.As traverses joined validation failures, while errors.Is continues
to find sentinel errors returned by compound options and their underlying
components.
Standard library
By default, the engine loads the full standard library. You can restrict it to a subset of groups or disable it entirely.
Available groups
| Group | Contents |
|---|---|
Types |
Type checking and conversion functions |
Strings |
String manipulation |
Math |
Mathematical operations |
Collections |
Generic collection operations |
DateTime |
Date and time functions |
Arrays |
Array-specific operations |
Objects |
Object-specific operations |
IO |
Expands to FS + NET |
FS |
File system access |
NET |
Network operations |
Path |
File path manipulation |
Utils |
Utility functions |
Testing |
Test assertion functions |
Selecting groups
stdlib.Safe() is equivalent to stdlib.Full().Without(stdlib.IO) — it removes file system and network access while keeping everything else.
Logging
Logging is configured at two levels: engine-wide defaults and per-session overrides.
Engine-level logging
Session-level logging
Per-session options override the engine defaults for that execution:
Log levels
| Level | Constant |
|---|---|
| Trace | ferret.LogTrace |
| Debug | ferret.LogDebug |
| Info | ferret.LogInfo |
| Warn | ferret.LogWarn |
| Error | ferret.LogError |
| Fatal | ferret.LogFatal |
| Panic | ferret.LogPanic |
| None | ferret.LogNone |
| Disabled | ferret.LogDisabled |
The constants and parsing helpers use the root ferret.LogLevel type. Parse a level supplied through configuration before constructing the engine:
ferret.MustParseLogLevel is available for trusted static configuration and panics when the input is invalid.
Reproducible randomness
In a runtime containing this API, provide a seed when reproducible execution is useful:
The same option works with Engine.Run and Plan.NewDebugSession. Zero and
negative seeds are valid; the last seed option wins. Reusing an option creates
independent sources. Without a seed, the Session obtains fresh operating-system
entropy once, on its first actual random draw. Construction and queries that never
draw do not read entropy. Explicit seeds require no entropy read. There is no
engine-wide RNG state or query-level seed mutation.
Within a Ferret version, the same seed, parameters, relevant inputs, and executed control flow produce the same random sequence. Sequential runs of an existing Session and debugger resumes continue the sequence. Create another Session with the same seed to replay it from the beginning. Exact sequences may change between Ferret versions. External I/O and timing can change control flow, including the number of jitter draws; a seed does not make those inputs deterministic.
Pseudo-random values are unsuitable for passwords, authentication tokens, secrets, keys, or other security-sensitive uses. Use cryptographic functionality for those purposes.
Output encoding
Query results are encoded before being returned as an Output. The default encoding is JSON; MessagePack is also built in. Set the content type at the session level with WithOutputContentType:
You can register custom codecs on the engine with WithEncodingCodec. See Value Encoders for the codec interfaces, hooks, registry, and a complete custom codec example.
ferret.WithEnvironmentOptions is an advanced escape hatch for applications that integrate directly with VM environment options. Prefer the root session options for parameters, logging, output encoding, and other ordinary embedding configuration.
Concurrency control
Three options control how the engine manages concurrent execution:
WithMaxActiveSessions
Limits the total number of sessions that can be running at the same time across the entire engine. When the limit is reached, plan.NewSession blocks until another session closes or the context is cancelled.
Use this to cap global resource consumption — CPU, memory, network, or downstream service pressure.
WithMaxVMsPerPlan
A hard cap on the total number of virtual machines a single plan can own, including both idle and active VMs. When the limit is reached and no idle VM is available, session creation fails with an error.
Use this to bound the resource footprint of a single frequently-executed query.
WithMaxIdleVMsPerPlan
Controls how many idle VMs each plan keeps cached for reuse after sessions close. When the idle cache is full, additional returned VMs are discarded instead of retained. The default is 8.
This is a memory-vs-latency trade-off: more idle VMs means faster session creation but higher steady-state memory usage.
How they work together
WithMaxActiveSessionsis an engine-wide concurrency gate — it controls how many sessions run at once across all plansWithMaxVMsPerPlanis a per-plan resource cap — it bounds the VM count for one specific compiled queryWithMaxIdleVMsPerPlanis a per-plan cache policy — it decides how many idle VMs stay warm
A value of 0 disables the limit for WithMaxActiveSessions and
WithMaxVMsPerPlan. For WithMaxIdleVMsPerPlan, 0 disables idle VM retention.
Sandboxed components
Sandboxed components control how scripts access host resources outside the Ferret runtime. Configure each component independently according to what the embedding application should allow. The engine adds the configured components to the context.Context passed to functions during execution.
File system
Scripts that use file system functions (from the FS stdlib group) operate within a sandboxed file system. The host can configure the root directory and restrict access to read-only.
If WithFSRoot is not set, file system access is disabled and FS functions return a root-not-configured error. WithFSReadOnly prevents scripts from writing within the configured root.
Accessing the file system from a function
Retrieve the configured file system from the function context. Prefer the narrowest helper for the operation the function performs:
The fs package also provides WriterFrom, DirectoriesFrom, and RemoverFrom. Use FileSystemFrom only when a function needs several capabilities.
For public modules, these context helpers are the recommended way to access files. They keep the module inside the root and read-only policy selected by the embedding host. Calling os.ReadFile, os.WriteFile, or constructing a separate file system would bypass that configuration.
HTTP client
Scripts that use network functions (from the NET stdlib group) make outbound requests through the engine’s network service. Supply a policy-configured HTTP client to restrict which destinations scripts can reach and how much data they can send or receive.
A network passed to WithNetwork remains caller-owned: Ferret never closes it,
including when engine construction fails. Ferret owns its default network and
networks created through WithNetworkOptions; it releases their idle connections
on construction failure or engine shutdown.
Network options apply in order. Replacing an Engine-created network releases its
idle connections immediately; replacing a borrowed network leaves it untouched.
The last network-setting option selects the service used by the engine.
WithNetworkOptions() without arguments leaves the current selection unchanged.
The HTTP client supports these policy controls:
| Control | Options |
|---|---|
| URL schemes and destinations | WithAllowedSchemes, WithAllowedHosts, WithBlockedHosts |
| Local, private, and link-local addresses | WithAllowLocalhost, WithAllowPrivateNetworks, WithAllowLinkLocal |
| Redirects | WithFollowRedirects, WithMaxRedirects |
| Request headers | WithDefaultHeader, WithDefaultHeaders, WithBlockedRequestHeaders |
| Time and payload limits | WithTimeout, WithNoTimeout, WithMaxRequestSize, WithUnlimitedRequestSize, WithMaxResponseSize, WithUnlimitedResponseSize, WithMaxResponseHeaderSize |
By default, the client allows HTTP and HTTPS and follows up to 10 redirects, but denies localhost, private networks, carrier-grade NAT, and link-local destinations. Unspecified, multicast, reserved, and invalid destinations are always denied. This includes cloud metadata endpoints on link-local addresses. Requests have a 30-second timeout, request and response bodies are limited to 16 MiB, and response headers are limited to 1 MiB.
Policy construction is fallible. A zero value passed to a numeric option restores that option’s secure default, while a negative value returns ferrethttp.ErrInvalidPolicyConfiguration. Disabling the timeout or a body-size limit requires WithNoTimeout, WithUnlimitedRequestSize, or WithUnlimitedResponseSize. Response headers always retain a finite limit. The zero value of ferrethttp.Policy is deny-all; call ferrethttp.NewPolicy to obtain the standard defaults when reusing a policy with another client.
Configuration errors support both errors.Is and errors.As. Use
github.com/ziflex/go-options.ValidationError to inspect the rejected field,
safe value, and reason:
Policy.Eval and Policy.Prepare accept a standard *net/http.Request. This is an intentional source break from the earlier Policy.Eval(*ferrethttp.Request) API: construct a standard outbound request, use Prepare when policy default headers should be added, or use Eval when the request is already complete. Eval does not mutate the request. Prepare adds missing defaults to Request.Header and then evaluates it; defaults added before a later validation failure remain on the request.
Neither method takes a separate context. Put cancellation and deadlines on the standard request with net/http.NewRequestWithContext or its WithContext method before sending it. Both methods require client-side outbound request state: server-side RequestURI and explicit transport-control state are rejected, as are Host overrides that target a different authority. With a finite request-body limit, every non-empty body must have a positive, known ContentLength; unknown-length streaming bodies return RequestBodyLengthError before transport. Buffer the body so net/http can determine its length, set a trustworthy length, or explicitly choose WithUnlimitedRequestSize.
Runtime denials can be inspected without parsing their messages:
Request header names and values are validated before transport. Connection, Content-Length, Host, Keep-Alive, Proxy-Authenticate, Proxy-Authorization, Proxy-Connection, TE, Trailer, Transfer-Encoding, and Upgrade are transport-controlled and cannot be supplied through Request.Header or configured as defaults.
The built-in client resolves hostnames and checks every returned address before the initial request and each followed redirect. It also checks the concrete numeric address immediately before connecting, so a DNS change between validation and connection cannot redirect the dial to a forbidden address. Redirects are checked before their requests are sent. An allowed-host list restricts names but never overrides these address-class checks.
For production, use the narrowest practical host allowlist, as in the example above. If an application deliberately needs internal destinations, enable each class separately:
WithAllowLocalhost enables only localhost names and loopback addresses. WithAllowPrivateNetworks enables RFC 1918, IPv6 unique-local, and carrier-grade NAT addresses; it does not enable link-local addresses. Enable WithAllowLinkLocal only when link-local access, including access to potential cloud metadata services, is explicitly required.
The built-in client does not inherit HTTP_PROXY, HTTPS_PROXY, or NO_PROXY. A proxy may resolve or connect to a different destination than the client validated. Applications that require a proxy or an exceptional destination class can inject their own ferrethttp.Client with ferretnet.WithHTTPClient; that client is responsible for equivalent destination and redirect controls.
Policy.Eval and Policy.Prepare are initial-request preflight checks, not replacements for the built-in secure transport. When a policy is reused with net/http or another custom transport, that integration is responsible for checking every redirect, validating all DNS results and the concrete dial address, applying the configured timeout, enforcing response-header and response-body limits, and cleaning up connections. A successful preflight does not make later redirect or resolution targets safe.
HTTP policy is defense in depth, not a complete sandbox for arbitrary untrusted FQL. Combine it with production allowlists, execution limits, restricted module sets, and infrastructure-level egress controls.
Accessing HTTP from a function
Retrieve the configured HTTP client from the function context and pass the same context to the request:
For public modules, use HTTPClientFrom instead of net/http or a separately constructed client. Requests then follow the host’s destination, redirect, header, timeout, and payload-size policies, while the execution context continues to carry cancellation and deadlines. Use NetworkFrom only when a function needs the complete network service.
Both examples have valid Ferret function signatures and can be registered with sdk.Func. See Develop a Ferret module for the complete registration pattern.
Compiler options
Set the optimization level for plans compiled by an engine with WithOptimizationLevel:
| Level | Constant | Description |
|---|---|---|
| None | ferret.OptimizationNone |
No optimizer passes; UDF calls retain caller frames |
| Basic | ferret.OptimizationBasic |
Tail-call elimination for proven-safe calls, constant propagation, liveness analysis, and peephole optimization |
| Full | ferret.OptimizationFull |
Basic pipeline plus register coalescing (default) |
Use the native ferret.WithPlanOptimizationLevel option to override that default for one compilation:
Omitting the option inherits the engine configuration. Explicit ferret.OptimizationNone, ferret.OptimizationBasic, and ferret.OptimizationFull affect only that plan, including concurrent compilations. Other levels are unsupported.
Debug compilation (engine.CompileDebug) accepts omission or explicit ferret.OptimizationNone and rejects other levels to preserve source-level debugging metadata. Compilation is synchronous and observes cancellation between phases; it does not preempt a parser already running.
Debug compilation retains UDF caller frames, including calls in return position. Tail-call elimination is optional and does not guarantee constant-space recursion: unoptimized and debug execution use stack space proportional to recursive call depth. See User-defined functions for the supported tail-call behavior.
Native ferret.PlanOption and ferret.SessionOption configure Ferret’s runtime directly and are distinct from Universal API functional options. Use the native ferret.With* factories for native engines and sessions.