Skip to content

Habit Schema

Workflows in Habits define automation sequences by connecting nodes that execute in order.

Full Schema

yaml
$schema: http://json-schema.org/draft-07/schema#

definitions:
  Record<string,any>:
    type: object

  # Logging configuration for workflow-level or stack-level logging
  LogLevel:
    type: string
    enum:
      - trace
      - debug
      - info
      - warn
      - error
      - fatal
      - none
    description: |
      Log level for filtering messages.
      trace: Most verbose, for detailed debugging
      debug: Debugging information
      info: General information (default)
      warn: Warnings, potential issues
      error: Errors, failures
      fatal: Critical errors, system failures
      none: Disable all logging

  LoggingConfig:
    type: object
    description: |
      Logging configuration. Can be specified in habit.yaml (per-workflow) or stack.yaml (global).
      Environment variables override all config (HABITS_LOG_LEVEL, HABITS_LOG_OUTPUT, etc.)
    properties:
      level:
        $ref: "#/definitions/LogLevel"
        description: Default log level for all loggers
        default: info
      outputs:
        type: array
        items:
          type: string
          enum:
            - console
            - file
            - json
        description: Output destinations (console, file, json)
        default: [console]
      file:
        type: object
        description: File output configuration (only used if 'file' is in outputs)
        properties:
          path:
            type: string
            description: Path to log file
            default: ./logs/habits.log
          maxSize:
            type: string
            description: Maximum file size before rotation (e.g., '10mb', '1gb')
            default: 10mb
          maxFiles:
            type: integer
            description: Maximum number of rotated files to keep
            default: 5
            minimum: 1
      format:
        type: string
        enum:
          - text
          - json
        description: Output format for console/file
        default: text
      colorize:
        type: boolean
        description: Enable ANSI colors in console output (auto-detected if not specified)
      bitOverrides:
        type: object
        description: |
          Per-bit log level overrides. Keys are bit names (e.g., 'bit-http', 'bit-openai').
          Values are log levels.
        additionalProperties:
          $ref: "#/definitions/LogLevel"

  WorkflowEdge:
    type: object
    properties:
      id:
        type: string
      source:
        type: string
      sourceHandle:
        type: string
      target:
        type: string
      targetHandle:
        type: string

  WorkflowNode:
    type: object
    properties:
      id:
        type: string
      type:
        type: string
        enum:
          - action
          - activepieces
          - n8n
          - trigger
          - script
          # Future/WIP type - used in conceptual docs examples
          - bit         # Custom bits (see roadmap/bits.md) - includes declarative, moderation, and other bit subtypes
      position:
        type: object
        properties:
          x:
            type: number
          y:
            type: number
      data:
        type: object
        properties:
          label:
            type: string
          framework:
            type: string
            enum:
              - activepieces
              - n8n
              - script
              # Future/WIP framework - used in conceptual docs examples
              - bits      # Custom bits framework (see roadmap/bits.md)
          module:
            type: string
          operation:
            type: string
          resource:
            type: string
          source:
            type: string
            description: Module source - 'local', 'npm', 'github', or 'link'
            enum:
              - local
              - npm
              - github
              - link
          registry:
            type: string
            description: |
              Custom npm registry URL for 'npm' source modules.
              If not provided, uses HABITS_NPM_REGISTRY_URL environment variable,
              or defaults to https://registry.npmjs.org.
              Useful for private registries like Verdaccio.
          isTrigger:
            type: boolean
            description: Marks this node as a trigger/entry point for the workflow
          inputs:
            type: array
            items:
              type: string
          outputs:
            type: array
            items:
              type: string
          inputTransforms:
            $ref: "#/definitions/Record<string,any>"
          credentials:
            $ref: "#/definitions/Record<string,any>"
          params:
            type: object
            additionalProperties: true
            properties:
              type:
                type: string
                description: Type parameter - 'script' 
              language:
                type: string
                enum:
                  - bash
                  - deno
                  - go
                  - python3
                  - sql
                  - typescript
                description: Script language for script framework nodes
              script:
                type: string
                description: The script code for script framework nodes
          stopAfterIf:
            type: object
            properties:
              expr:
                type: string
              skipIfStopped:
                type: boolean

type: object
properties:
  id:
    type: string
  name:
    type: string
  description:
    type: string
  version:
    type: string
  nodes:
    type: array
    items:
      $ref: "#/definitions/WorkflowNode"
  edges:
    type: array
    items:
      $ref: "#/definitions/WorkflowEdge"
  output:
    type: object
    description: Output mapping for workflow results
    additionalProperties:
      type: string
  logging:
    $ref: "#/definitions/LoggingConfig"
    description: |
      Per-workflow logging configuration (optional).
      Overridden by stack.yaml logging config, which is overridden by environment variables.

Workflow Structure

FieldTypeRequiredDescription
idstringYesUnique identifier for the workflow
namestringYesHuman-readable name
descriptionstringNoWorkflow description
inputsobjectNoInput parameters with types and defaults
nodesarrayYesList of node definitions
edgesarrayYesNode connections defining execution flow
outputobjectNoOutput mapping for workflow results

Nodes

Each node represents a single operation in your workflow.

Node Fields

FieldTypeDescription
idstringUnique identifier for the node
typestringactivepieces, n8n, script, or bit
positionobjectOptional { x, y } coordinates for visual layout
dataobjectNode configuration (see below)

Node Data Fields

FieldTypeDescription
labelstringDisplay name for the node
frameworkstringactivepieces, n8n, script, or bits
modulestringModule/package name (e.g., @activepieces/piece-openai)
operationstringOperation to execute
resourcestringResource type (n8n nodes)
sourcestringModule source (npm, inline)
isTriggerbooleanMarks node as workflow entry point
paramsobjectOperation parameters
credentialsobjectAuthentication credentials
inputTransformsobjectInput data transformations
stopAfterIfobjectConditional stop with expr and skipIfStopped

Script Params

For type: script nodes:

FieldTypeDescription
typestringSet to "script"
languagestringbash, deno, go, python3, sql, or typescript
scriptstringThe script code to execute

Edges

Edges define how data flows between nodes.

FieldTypeRequiredDescription
idstringNoUnique identifier for the edge
sourcestringYesID of the source node
targetstringYesID of the target node
sourceHandlestringNoOutput handle on source node
targetHandlestringNoInput handle on target node

Data References

Reference data from other nodes using template syntax:

PatternDescription
{{<nodeId>}}Full output from a specific node
{{<nodeId>.<path>}}Nested property from node output (JSON)
{{habits.env.<VAR>}}Environment variable
{{habits.input.<field>}}Runtime input parameter
{{habits.header.<header>}}Request header
{{habits.cookies.<cookie>}}Request cookie

Stack Configuration

The stack.yaml file defines workflows and server settings:

FieldTypeDescription
versionstringConfig version
workflowsarrayWorkflow paths or inline definitions
server.portnumberServer port (default: 3000)
server.hoststringServer host (default: 0.0.0.0)
loggingobjectLogging configuration

Examples

See complete working examples:

yaml
id: text-to-voice-to-s3
name: text-to-voice-to-s3

nodes:
  - id: generate-text
    type: activepieces
    data:
      framework: activepieces
      module: '@activepieces/piece-openai'
      operation: ask_chatgpt
      source: npm
      credentials:
        apiKey: '{{habits.env.OPENAI_API_KEY}}'
      params:
        prompt: 'Write a short 2-sentence motivational quote related to: {{habits.input.prompt}}'
        model: gpt-4o-mini
  - id: text-to-speech
    type: n8n
    data:
      framework: n8n
      module: n8n-nodes-elevenlabs
      operation: text-to-speech
      source: npm
      credentials:
        elevenLabsApi:
          xiApiKey: '{{habits.env.ELEVENLABS_API_KEY}}'
      params:
        resource: speech
        text: '{{generate-text}}'
        voice_id: 21m00Tcm4TlvDq8ikWAM
  - id: save-locally
    type: script
    data:
      label: Save Audio Locally
      framework: script
      source: inline
      params:
        audio: '{{text-to-speech.result.0.base64}}'
        language: deno
        script: |
          export async function main(audio: string | ArrayBuffer) {
            const outputDir = '/tmp/habits-audio';
            await Deno.mkdir(outputDir, { recursive: true });
            
            const filename = `audio-${Date.now()}.mp3`;
            const filepath = `${outputDir}/${filename}`;
            
            // Use Buffer for base64 decoding (works in Node.js environment)
            const buffer = typeof audio === 'string' 
              ? Buffer.from(audio, 'base64')
              : new Uint8Array(audio);
            
            await Deno.writeFile(filepath, buffer);
            
            const stats = await Deno.stat(filepath);
            return { 
              success: true, 
              filepath,
              filename,
              size: stats.size,
              modifiedAt: stats.mtime?.toISOString()
            };
          }
edges:
  - source: generate-text
    target: text-to-speech
  - source: text-to-speech
    target: save-locally
yaml
version: "1.0"
workflows:
  - id: text-to-voice-to-s3
    path: ./habit.yaml
    enabled: true
server:
  port: 13000
  host: "0.0.0.0"
  frontend: ./frontend


logging:
  level: info                    # trace, debug, info, warn, error, fatal, none
  outputs: [console]             # console, file, json
  format: text                   # text or json
  colorize: true
yaml
id: habit-code
name: Habit Code Demo
description: A habit demonstrating if conditions, loops, and string utilities using pure script nodes (Deno)

# This habit demonstrates using Deno scripts for:
# 1. If conditions - Check if data is valid
# 2. Loops - Iterate over items and transform them
# 3. String utils - Split, join, uppercase, lowercase operations

nodes:
  # Node 1: Create test data
  - id: create-data
    type: script
    data:
      framework: script
      source: inline
      label: Create Test Data
      params:
        type: script
        language: deno
        script: |
          export async function main() {
            console.log("📝 Creating test data");
            
            return {
              name: "habits demo",
              items: "apple,banana,cherry,date",
              count: 4,
              isValid: true
            };
          }

  # Node 2: If condition - Check if data is valid
  - id: check-valid
    type: script
    data:
      framework: script
      source: inline
      label: If Condition Check
      params:
        isValid: "{{create-data.result.isValid}}"
        count: "{{create-data.result.count}}"
        type: script
        language: deno
        script: |
          export async function main(isValid: boolean, count: number) {
            console.log("🔀 Running if condition check");
            
            // IF condition: check if data is valid and count > 0
            if (isValid && count > 0) {
              return {
                passed: true,
                message: "Data validation passed",
                branch: "trueBranch"
              };
            } else {
              return {
                passed: false,
                message: "Data validation failed",
                branch: "falseBranch"
              };
            }
          }

  # Node 3: String utils - Transform text
  - id: string-utils
    type: script
    data:
      framework: script
      source: inline
      label: String Utilities
      params:
        text: "{{create-data.result.name}}"
        type: script
        language: deno
        script: |
          export async function main(text: string) {
            console.log("📝 Running string utilities");
            
            return {
              original: text,
              uppercase: text.toUpperCase(),
              lowercase: text.toLowerCase(),
              titleCase: text.split(' ').map(word => 
                word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()
              ).join(' '),
              reversed: text.split('').reverse().join(''),
              length: text.length,
              words: text.split(' ').length
            };
          }

  # Node 4: Loop - Split and iterate over items
  - id: loop-items
    type: script
    data:
      framework: script
      source: inline
      label: Loop Over Items
      params:
        itemsString: "{{create-data.result.items}}"
        type: script
        language: deno
        script: |
          export async function main(itemsString: string) {
            console.log("🔄 Running loop over items");
            
            // Split the string into array
            const items = itemsString.split(',').map(item => item.trim());
            
            // Loop through items and transform each
            const transformedItems = [];
            for (let i = 0; i < items.length; i++) {
              const item = items[i];
              transformedItems.push({
                index: i,
                original: item,
                uppercase: item.toUpperCase(),
                length: item.length,
                startsWithVowel: /^[aeiou]/i.test(item)
              });
            }
            
            // Aggregate results
            const vowelStarters = transformedItems.filter(item => item.startsWithVowel);
            
            return {
              totalItems: items.length,
              items: transformedItems,
              joinedUppercase: transformedItems.map(i => i.uppercase).join(' | '),
              vowelStarters: vowelStarters.map(i => i.original),
              vowelStarterCount: vowelStarters.length
            };
          }

  # Node 5: Final summary combining all results
  - id: summary
    type: script
    data:
      framework: script
      source: inline
      label: Generate Summary
      params:
        validation: "{{check-valid.result}}"
        stringOps: "{{string-utils.result}}"
        loopResult: "{{loop-items.result}}"
        type: script
        language: deno
        script: |
          export async function main(validation: any, stringOps: any, loopResult: any) {
            console.log("📊 Generating final summary");
            
            return {
              validationPassed: validation.passed,
              validationMessage: validation.message,
              transformedName: stringOps.titleCase,
              allStringTransforms: stringOps,
              totalItemsProcessed: loopResult.totalItems,
              itemsWithVowels: loopResult.vowelStarters,
              processedItems: loopResult.items,
              processedAt: new Date().toISOString(),
              summary: `Processed ${loopResult.totalItems} items. Validation: ${validation.passed ? 'PASSED' : 'FAILED'}. Name transformed to: ${stringOps.titleCase}`
            };
          }

edges:
  - source: create-data
    target: check-valid
  - source: create-data
    target: string-utils
  - source: create-data
    target: loop-items
  - source: check-valid
    target: summary
  - source: string-utils
    target: summary
  - source: loop-items
    target: summary

output:
  summary: "{{summary.result}}"
  validation: "{{check-valid.result}}"
  stringOperations: "{{string-utils.result}}"
  loopResults: "{{loop-items.result}}"

Next Steps

Released under the Apache 2.0 License.