Intents

An intent is a command submitted to causet-runtime. It expresses the caller’s desired state change — the runtime decides whether to apply it, and what events result.


What an Intent Is

Intents are the only way to write to Causet. You do not directly insert events or mutate entity state. You submit an intent — the runtime evaluates your rules and emits events.

This distinction matters:

  • Intent: “I want the user to follow this artist.”
  • Event: “The user followed this artist.” (fact — emitted if rules allow it)

Intent Structure

FieldTypeDescription
actionstringThe action name declared in the DSL (e.g. FOLLOW_ARTIST)
entityIdstringThe entity this intent targets
forkIdstringThe fork this intent is scoped to
payloadobjectAction-specific input fields, validated against the action’s input schema

Example:

{
  "action": "FOLLOW_ARTIST",
  "entityId": "user-123",
  "forkId": "production",
  "payload": {
    "user_id": "user-123",
    "artist_id": "artist-456"
  }
}

The payload fields must match the input schema declared for the action in the DSL:

actions:
  FOLLOW_ARTIST:
    state: user
    input:
      user_id:   { type: string, required: true }
      artist_id: { type: string, required: true }

Submitting Intents

Via SaaS API

POST /v1/intents
Authorization: Bearer <api-key>
Content-Type: application/json
 
{
  "action": "FOLLOW_ARTIST",
  "entityId": "user-123",
  "forkId": "production",
  "payload": {
    "user_id": "user-123",
    "artist_id": "artist-456"
  }
}

The SaaS API authenticates the request, resolves the fork, and proxies to causet-runtime.

Directly to causet-runtime

For server-to-server calls from trusted services:

POST /intent
Authorization: Bearer <service-credential>
Content-Type: application/json
 
{
  "action": "FOLLOW_ARTIST",
  "entityId": "user-123",
  "forkId": "production",
  "payload": {
    "user_id": "user-123",
    "artist_id": "artist-456"
  }
}

Intent Validation

Before rule evaluation, the runtime validates:

  1. Action exists — the named action must be declared in the active IR for the fork. Unknown actions return a 404.
  2. Required fields present — all required: true input fields must be in the payload. Missing fields return a 422.
  3. Field types — payload values must be coercible to the declared types. Type mismatches return a 422.

These checks happen before cursor lock acquisition and rule evaluation.


Preflight Rejection

After validation, preflight rules run. A reject op ends intent processing:

preflight:
  rules:
    - name: reject_self_follow
      when: { expr: "intent.user_id == intent.artist_id" }
      then:
        - op: reject
          code: CANNOT_FOLLOW_SELF
          message: "A user cannot follow themselves."

Rejection response:

{
  "status": "REJECTED",
  "intentId": "i-abc123",
  "code": "CANNOT_FOLLOW_SELF",
  "message": "A user cannot follow themselves."
}

No ledger events are written on rejection.


Successful Response

On success, the runtime returns the intent ID and all emitted events:

{
  "status": "SUCCESS",
  "intentId": "i-xyz789",
  "events": [
    {
      "eventType": "ARTIST_FOLLOWED",
      "entityId": "user-123",
      "ts": 1719331200000,
      "payload": {
        "user_id": "user-123",
        "artist_id": "artist-456"
      }
    }
  ]
}

This response is returned after the ledger commit. The events in the response are durable.


intent_status Table

Every processed intent (success or rejection) is recorded in the intent_status table in the causet database:

ColumnDescription
idIntent UUID (returned as intentId)
actionAction name
entity_idTarget entity
fork_idFork
statusSUCCESS, REJECTED, ERROR
rejection_codePopulated on REJECTED
error_messagePopulated on ERROR
processed_atTimestamp
ir_versionIR version used for evaluation

Intent Deduplication

Causet does not provide built-in idempotency keys at the HTTP layer. For idempotent operations, use deterministic entity IDs — submitting the same intent twice against an entity where the preflight rule rejects duplicates is the recommended pattern:

preflight:
  rules:
    - name: reject_already_following
      when: { expr: "state.following contains intent.artist_id" }
      then:
        - op: reject
          code: ALREADY_FOLLOWING

The deterministic entity ID approach means: if you construct the same entityId from the same input, the entity’s current state will naturally gate duplicate processing.


Error Codes

HTTP StatusMeaning
200Intent processed (may be SUCCESS or REJECTED)
400Malformed request body
404Unknown action name for the fork’s active IR
422Input validation failure (missing required fields, type mismatch)
500Internal error — transient DB or Kafka failure
503Runtime not ready (health check failing)

Preflight rejections return 200 with status: REJECTED — they are a normal outcome, not an error.