Continuous Execution (loop, socket) ​
While a function executes once in response to a direct trigger (like a network request or a button click), a loop is an autonomous, continuous workflow.
Loops are the engine behind your background workers, CRON jobs, stream listeners, and polling mechanisms. Just like functions, you do not write the implementation code. You define the trigger conditions and the permissions, and the Carotene compiler generates a strictly sandboxed, auto-scaling background process.
Availability & Scope ​
A loop is a continuous, autonomous process. You can define a loop in either a backend or frontend block. The Carotene compiler automatically selects the optimal infrastructure based on the context:
- backend loop: The compiler generates a long-running server-side process, optimised for background workers, stream consumption (e.g., Redis/Kafka), or scheduled CRON jobs.
- frontend loop: The compiler generates a high-performance client-side task, optimised for the browser main thread (e.g.,
requestAnimationFramefor game loops) or a background WebWorker (for heavy client-side computations) to keep the UI responsive.
Defining the Trigger (config) ​
Because Carotene is infrastructure-aware, it needs to know how this background process should run. You define this using a typed config block. The compiler uses this block to provision the exact underlying infrastructure (e.g., AWS EventBridge, a detached Redis worker, or a Kafka consumer).
1. Scheduled Execution (CRON) ​
For tasks that need to run at specific intervals (like nightly billing or cleaning up expired sessions), you configure a cronloop.
backend {
// A background worker that runs autonomously
loop AbandonedCartRecovery {
config {
cron: { schedule: "0 * * * *" } // Runs at minute 0 past every hour
}
reads store.Cart
calls integration.SendGrid.SendEmail
updates store.Cart
implements {
@("Find abandoned carts, send emails, and update their status")
}
}
}2. Event-Driven Execution (Streams & Listeners) ​
For real-time applications, you often need a worker to constantly listen for changes in your volatile state or listen to an external event stream.
backend {
// A worker that constantly processes a Redis queue
loop MatchmakingWorker {
config {
stream: { listensTo: state.MatchmakingQueue }
}
// The Sandbox permissions
reads state.MatchmakingQueue
creates store.GameSession
mutates state.MatchmakingQueue
implements {
@("Process matchmaking queue and create game sessions")
}
}
}The Sandbox & Verb Inheritance ​
The most powerful aspect of the loop primitive is that it uses the exact same Zero-Trust Permission Matrix as a function.
When the Carotene CLI hands your AbandonedCartRecovery loop to the AI code generator, the execution sandbox is built strictly from the verbs you provided.
- The AI receives
ctx.db.cart.read()andctx.db.cart.update(). - The AI receives
ctx.SendGrid.SendEmail().
If the AI hallucinates and attempts to read the store.User table to personalize the email, the compiler instantly fails the build. If a permission is not explicitly granted at the architectural level, the background worker physically cannot execute it.
The Infinite Loop Guardrail (Execution Bounds) ​
AI coding agents (and human developers) are notorious for creating background workers that get trapped in infinite loops or memory leaks, bringing down production servers.
Carotene protects your infrastructure by enforcing Execution Bounds at the compiler level. When Carotene generates the runtime for a loop, it wraps the AI's implementation in a strict supervisor process.
By default, Carotene enforces the following guardrails on every loop:
- Strict Timeouts: If a single iteration of a
cronloop takes longer than the default maximum (e.g., 5 minutes), the supervisor terminates the worker and logs a fatal error. - Resource Limits: If a
streamloop detects a memory spike characteristic of an unhandledwhile(true)hallucination, the supervisor kills the process and restarts it with a clean memory heap.
You can override these safety bounds explicitly in the config block if your architecture requires heavy, long-running batch processing:
backend {
loop HeavyVideoEncoding {
config {
stream: {
listensTo: state.EncodingQueue
timeout: "2h" // Explicitly granting a 2-hour execution bound
memoryLimit: "4GB"
}
}
reads store.VideoFile
updates store.VideoFile
implements {
@("Encode the video file and update the status")
}
}
}By making infrastructure constraints declarative, you ensure that the AI excavator cannot accidentally deploy a worker that drains your cloud budget overnight.
Real-Time Networking (socket) ​
While loop handles background processing and queues, the socket primitive is used for continuous, bidirectional real-time communication with clients (e.g., WebSockets).
A socket is defined with a signature that resembles a function, but its implements block runs once per received message. Returning a value from the block automatically emits that value back to the client's open stream.
backend {
socket HandleChat(msg: String) -> String {
reads store.Message
creates store.Message
implements {
@("Save the incoming message and return a broadcast string")
}
}
}