Skip to content

Functions & Side-Effects ​

In Carotene, a function is a discrete, triggered workflow. It represents an action that runs in response to a user request or an event, and it always returns a finite result (a Future).

Crucially, you do not write the implementation code inside your .carrot file. Instead, you use the function block to define a Zero-Trust Permission Matrix. You define the inputs, the outputs, and the exact side-effects the AI agent is allowed to execute.

The Execution Sandbox ​

When the Carotene CLI hands your function contract to the AI code generator, it builds a strictly sandboxed environment based entirely on the verbs you provide.

If you do not grant a permission at the architectural level, the AI physically cannot perform the action in the codebase.

dart
domain Commerce {
  
  backend {
    // 1. The Signature: Defines the exact network payload (DTO vs Entity Binding)
    function ProcessCheckout(payload: CheckoutPayload) -> Future<store.Order> {
      
      // 2. The Permission Matrix (Verbs)
      reads store.Product      // Allowed to check stock
      creates store.Order      // Allowed to insert the new order
      mutates state.Metrics    // Allowed to update the volatile memory cache
    }
  }
}

How the Sandbox Enforces the Rules ​

The AI receives an execution context (ctx). Because of the verbs above, the AI's context contains ctx.db.product.read()and ctx.db.order.create().

If the hallucinating AI writes await ctx.db.user.delete(), the generated TypeScript/Go compiler instantly fails. The database client injected into this specific function does not possess the delete method for the User table. The AI is trapped inside the exact bounds of your architectural intent.

Integrations (Managing External Side-Effects) ​

Database access is only half the battle. Modern applications rely heavily on third-party APIs (Stripe, SendGrid, Twilio).

In standard Node.js/Python, an AI can simply write fetch('https://any-url.com'). This is a massive security vulnerability. In Carotene, raw HTTP requests are banned in the execution sandbox. To talk to the outside world, you must define an integration and grant the AI permission to use it via the calls verb.

1. Defining the Integration ​

You define external services in the global scope, treating them as external functions.

dart
domain ExternalServices {
  
  integration Stripe {
    function ChargeCustomer(amount: Float, source: String) -> Future<Boolean>
  }

  integration SendGrid {
    function SendEmail(to: String, templateId: String)
  }
}

2. Granting the Side-Effect ​

Now, inside your business logic, you explicitly grant the AI permission to trigger these external services.

dart
backend {
  function CompleteOrder(orderId: store.Order.id) {
    reads store.Order
    
    // Explicitly grants the AI permission to trigger these exact side-effects
    calls integration.Stripe.ChargeCustomer
    calls integration.SendGrid.SendEmail
    
    triggers store.Order.status.ProcessPayment
    updates store.Order
  }
}

The Security Guarantee: The AI can call ctx.Stripe.ChargeCustomer(). But if it tries to write a raw fetch() or use axiosto send data to an unauthorized URL, the sandbox rejects the code. You have total architectural control over data egress.

Full-Stack RPC (Crossing the Network Boundary) ​

Functions do not just live on the backend. Carotene is a full-stack language, meaning your frontend UI components need to trigger these backend workflows.

You do this using the exact same calls verb, completely eliminating the need to write REST API clients, fetch wrappers, or GraphQL queries.

dart
frontend {
  // A UI component definition
  view CheckoutButton(currentOrder: backend.store.Order) {
    
    // The frontend explicitly calls the backend function, passing the exact required type
    calls backend.CompleteOrder(currentOrder.id)
  }
}

Auto-Generated UI States (isLoading) ​

When the Carotene compiler sees a frontend block calling a backend function, it automatically generates the end-to-end RPC plumbing (like tRPC or gRPC).

But it goes a step further for UI developers. For discrete functions (non-streams), the generated frontend SDK automatically exposes reactive listeners. Just as the state machine generates .canExecute(), the RPC layer generates an .isLoading property.

dart
// Inside your generated React/Vue UI component
<button 
  disabled={!backend.CompleteOrder.canExecute(currentOrder.status) || backend.CompleteOrder.isLoading}
  onClick={() => backend.CompleteOrder(currentOrder.id)}
>
  {backend.CompleteOrder.isLoading ? "Processing..." : "Pay Now"}
</button>

By generating these properties natively in the SDK, Carotene eliminates the need for developers (and AI UI generators) to write tedious useState(false) boilerplate to manage network lifecycles.