Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
constructive-io avatar

Constructive Jobs

  • 3 installs
  • Updated August 4, 2026
  • constructive-io/constructive-skills

Background job system with JobTrigger blueprint nodes for enqueuing jobs on row changes.

About

Constructive Jobs Background job infrastructure for the Constructive platform.. Declaratively attach triggers to tables that enqueue jobs when rows change.

  • Adding a background job that fires on row INSERT/UPDATE/DELETE
  • Wiring a table to a Knative cloud function

Constructive Jobs by the numbers

  • 3 all-time installs (skills.sh)
  • Ranked #1,816 of 2,715 Automation & Workflows skills by installs in the Skillselion catalog
  • Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/constructive-io/constructive-skills --skill constructive-jobs

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs3
Last updatedAugust 4, 2026
Repositoryconstructive-io/constructive-skills

What it does

Background job system with JobTrigger blueprint nodes for enqueuing jobs on row changes.

Files

SKILL.mdMarkdownGitHub ↗

Constructive Jobs

Background job infrastructure for the Constructive platform. Declaratively attach triggers to tables that enqueue jobs when rows change, processed by the Knative worker stack.

When to Apply

  • Adding a background job that fires on row INSERT/UPDATE/DELETE
  • Wiring a table to a Knative cloud function (e.g., send email on invite creation)
  • Syncing data to external systems on change (e.g., Stripe sync on invoice update)
  • Generating embeddings, sending notifications, auditing changes
  • Scheduling recurring jobs (cron-style)
  • Adding file/image embeddings to a storage table

Architecture

Table row change (INSERT/UPDATE/DELETE)
  --> PostgreSQL AFTER trigger (created by JobTrigger node)
    --> app_jobs.add_job(task_identifier, payload)
      --> knative-job-worker polls app_jobs.jobs
        --> POST ${KNATIVE_SERVICE_URL}/${task_identifier}
          --> Knative function handles the job

The database extension pgpm-database-jobs provides:

  • app_jobs.jobs — queued/running jobs table
  • app_jobs.scheduled_jobs — cron-style scheduled jobs table
  • app_jobs.add_job() — enqueue a one-off job
  • app_jobs.add_scheduled_job() — register a recurring job

The JobTrigger blueprint node automatically creates the PostgreSQL triggers that call app_jobs.add_job().

JobTrigger Blueprint Node

Add to a table's nodes[] in a blueprint definition to auto-create triggers:

{
  ref: 'invoices',
  table_name: 'invoices',
  nodes: [
    ...ORG_NODES,
    {
      $type: 'JobTrigger',
      data: {
        task_identifier: 'process_invoice',
      }
    },
  ],
  fields: [
    { name: 'amount', type: { name: 'numeric' }, is_required: true },
    { name: 'status', type: { name: 'text' }, default_value: { value: 'draft' } },
  ],
}

This creates INSERT and UPDATE triggers that enqueue a process_invoice job with { id: row.id } as the payload.

Configuration Reference

ParameterTypeDefaultDescription
task_identifierstring(required)Job name passed to add_job (e.g., process_invoice, sync_to_stripe)
payload_strategy"row" \"row_id" \"fields" \
payload_fieldsstring[]Column names for fields strategy
payload_customobjectKey-to-column mapping for custom strategy
events`("INSERT" \"UPDATE" \"DELETE")[]`
watch_fieldsstring[]For UPDATE: only fire when these columns change
condition_fieldstringLegacy: column for simple equality WHEN clause
condition_valuestringLegacy: value to match for condition_field
conditionsobject \array
include_oldbooleanfalseInclude OLD row in UPDATE payload
include_metabooleanfalseInclude table/schema metadata in payload
job_keystringStatic key for upsert semantics (deduplication)
queue_namestringRoute to a specific worker queue
priorityinteger0Lower = higher priority
run_at_delaystringPostgreSQL interval delay (e.g., '30 seconds')
max_attemptsinteger25Maximum retry attempts
entity_fieldstring (column-ref)Column holding (or referencing) the entity_id. Forwarded to the job payload for entity context. For FK lookups, combine with entity_lookup.
entity_lookupobjectFK lookup config: { obj_table, obj_schema?, obj_field }. Resolves entity_id through a related table when entity_field is a FK.

Constraints: conditions, condition_field, and watch_fields are mutually exclusive — only one can be specified per trigger.

Compound Conditions

The conditions parameter accepts a structured JSON syntax for complex WHEN clauses. Column types are resolved automatically from the PostgreSQL schema — values in JSON are cast to the correct type at generation time. This system is shared with EventTracker (see `constructive-events`) — both use the same build_condition_ast() function and conditionProperties schema.

Leaf condition:

{ field: 'status', op: '=', value: 'ready', row: 'NEW' }
KeyRequiredDefaultDescription
fieldyesColumn name (validated against the table)
opyes=, !=, >, <, >=, <=, LIKE, NOT LIKE, IS NULL, IS NOT NULL, IS DISTINCT FROM
valueconditionalComparison value (omit for IS NULL, IS NOT NULL, IS DISTINCT FROM)
rowno'NEW'Row reference: 'NEW' or 'OLD'
refnoColumn reference for field-to-field comparison: { field: '...', row: '...' }

Array shorthand (implicit AND):

conditions: [
  { field: 'status', op: '=', value: 'ready' },
  { field: 'status', op: '=', value: 'pending', row: 'OLD' },
  { field: 'mime_type', op: 'LIKE', value: 'image/%' },
]

Nested combinators (AND/OR/NOT):

conditions: {
  AND: [
    { field: 'status', op: '=', value: 'ready' },
    { OR: [
      { field: 'mime_type', op: 'LIKE', value: 'image/%' },
      { field: 'mime_type', op: 'LIKE', value: 'video/%' },
    ]},
    { NOT: { field: 'is_draft', op: '=', value: true } },
  ]
}

See references/common-patterns.md for full blueprint examples.

Payload Strategies

See references/payload-strategies.md for detailed examples of each strategy.

StrategyPayload shapeUse case
row_id (default){ "id": "<uuid>" }Lightweight; function fetches full data
rowFull NEW/OLD row as JSONAudit trail, full-context processing
fieldsSelected columns onlyMinimize payload; send only what's needed
customMapped key namesReshape column names for external APIs

Common Patterns

See references/common-patterns.md for full blueprint examples of:

  • Conditional triggers (watch_fields, condition_field, conditions)
  • Compound conditions (status transitions, MIME type filtering)
  • Delayed/debounced jobs (run_at_delay + job_key)
  • Multiple triggers per table
  • Email on invite, Stripe sync, audit trail, webhook dispatch

ProcessFileEmbedding Blueprint Node

Generic, MIME-scoped embedding node for file/storage tables. Composes SearchVector + JobTrigger + ProcessChunks internally. Supports two modes:

  • Direct mode (default): whole-file to single vector (e.g., CLIP for images). No extraction config.
  • Extract mode: file to text to chunks to per-chunk vectors. Enabled by providing extraction config.

Multiple instances can coexist on the same table with different MIME scopes, field names, and embedding strategies.

Direct Mode (single vector per file)

// Image embeddings via CLIP — one vector per image file
{
  ref: 'files',
  table_name: 'files',
  nodes: [
    ...STORAGE_NODES,
    { $type: 'ProcessFileEmbedding', data: {
      mime_patterns: ['image/%'],
      dimensions: 512,
      task_identifier: 'process_image_embedding',
    }},
  ],
}

Extract Mode (file to text to chunks to vectors)

// Document embeddings — extract text, chunk, embed each chunk
{
  ref: 'files',
  table_name: 'files',
  nodes: [
    ...STORAGE_NODES,
    { $type: 'ProcessFileEmbedding', data: {
      mime_patterns: ['application/pdf', 'text/%', 'application/vnd.openxmlformats-officedocument.*'],
      dimensions: 768,
      task_identifier: 'process_document_extraction',
      extraction: {
        text_field: 'extracted_text',
        metadata_field: 'extracted_metadata',
      },
      // chunks are enabled by default in extract mode
      chunks: {
        chunk_size: 1000,
        chunk_overlap: 200,
        chunk_strategy: 'paragraph',
      },
    }},
  ],
}

Multi-Modal: Multiple Pipelines on One Table

// Knowledge base — three embedding pipelines on one files table
{
  ref: 'files',
  table_name: 'files',
  nodes: [
    ...STORAGE_NODES,

    // Pipeline 1: CLIP visual embeddings for images
    { $type: 'ProcessFileEmbedding', data: {
      field_name: 'image_embedding',
      mime_patterns: ['image/%'],
      dimensions: 512,
      task_identifier: 'process_image_embedding',
    }},

    // Pipeline 2: Text extraction + chunked embeddings for documents
    { $type: 'ProcessFileEmbedding', data: {
      field_name: 'document_embedding',
      mime_patterns: ['application/pdf', 'text/%', 'application/vnd.openxmlformats-officedocument.*'],
      dimensions: 768,
      task_identifier: 'process_document_extraction',
      extraction: {
        text_field: 'extracted_text',
        metadata_field: 'extracted_metadata',
      },
    }},

    // Pipeline 3: Audio/video transcription + chunked embeddings
    { $type: 'ProcessFileEmbedding', data: {
      field_name: 'media_embedding',
      mime_patterns: ['audio/%', 'video/%'],
      dimensions: 768,
      task_identifier: 'process_media_transcription',
      extraction: {
        text_field: 'transcription_text',
        metadata_field: 'transcription_metadata',
      },
    }},
  ],
}

Configuration Reference

ParameterTypeDefaultDescription
field_namestring'embedding'Vector column name
dimensionsinteger768Vector dimensions (512 for CLIP, 768 for nomic, 1536 for ada-002)
index_method'hnsw' \'ivfflat''hnsw'
metric'cosine' \'l2' \'ip'
index_optionsobject{}Index tuning params (e.g. {m: 16, ef_construction: 64})
mime_patternsstring[]['image/%']MIME LIKE patterns (OR'd together)
task_identifierstring'process_file_embedding'Job task name
eventsstring[]['INSERT']Trigger events
payload_customobject{file_id: 'id', key: 'key', mime_type: 'mime_type', bucket_id: 'bucket_id'}Payload mapping
trigger_conditionsobject \array
extractionobjectEnables extract mode. Sub-keys: text_field, metadata_field
include_chunksbooleantrue in extract mode, false in directWhether to create a chunks table via ProcessChunks
chunksobjectChunking config: chunk_size, chunk_overlap, chunk_strategy, metadata_fields, etc.

ProcessImageEmbedding Blueprint Node

Image-specific preset of ProcessFileEmbedding. Delegates entirely to ProcessFileEmbedding with image-oriented defaults.

// Minimal — uses all defaults (512d CLIP, image/%, process_image_embedding)
{
  ref: 'files',
  table_name: 'files',
  nodes: [
    ...STORAGE_NODES,
    { $type: 'ProcessImageEmbedding' },
  ],
}

Default overrides vs ProcessFileEmbedding:

ParameterProcessImageEmbedding defaultProcessFileEmbedding default
dimensions512768
task_identifier'process_image_embedding''process_file_embedding'
mime_patterns['image/%']['image/%']

All ProcessFileEmbedding parameters are accepted and forwarded through. You can use ProcessImageEmbedding with extraction to enable OCR-based text extraction from images.

ProcessChunks Blueprint Node

Standalone chunking node that creates a child chunks table for any parent table. Composed internally by ProcessFileEmbedding (enabled by default in extract mode), but can also be used standalone.

The chunks table gets:

  • FK to parent (CASCADE delete)
  • content text field
  • chunk_index integer
  • embedding vector(N) with HNSW index
  • metadata jsonb
  • RLS policies inherited from parent
  • Optional job trigger for automatic chunking

Standalone Usage

// Add chunking to any table with text content
{
  ref: 'articles',
  table_name: 'articles',
  nodes: [
    'DataId',
    'DataTimestamps',
    { $type: 'ProcessChunks', data: {
      chunk_size: 1000,
      chunk_overlap: 200,
      chunk_strategy: 'paragraph',
      dimensions: 768,
    }},
  ],
  fields: [
    { name: 'title', type: { name: 'text' }, is_required: true },
    { name: 'body', type: { name: 'text' } },
  ],
}

Configuration Reference

ParameterTypeDefaultDescription
content_field_namestring'content'Text column in chunks table
chunk_sizeinteger1000Max characters per chunk
chunk_overlapinteger200Overlapping characters between chunks
chunk_strategy'fixed' \'sentence' \'paragraph' \
dimensionsinteger768Per-chunk embedding dimensions
metric'cosine' \'l2' \'ip'
chunks_table_namestring'{parent}_chunks'Override table name
metadata_fieldsstring[]Parent fields to copy into chunk metadata
enqueue_chunking_jobbooleantrueAuto-enqueue chunking job
chunking_task_namestring'generate_chunks'Job task name

Knative Worker Stack

The runtime consists of three packages:

PackageRole
@constructive-io/knative-job-serviceOrchestrator — starts worker + callback server + scheduler
@constructive-io/knative-job-workerPolls app_jobs.jobs, POSTs to function URL
@constructive-io/knative-job-fnExpress app factory for function handlers

Job Flow

1. Trigger fires -> inserts row into app_jobs.jobs 2. Worker polls -> picks up job by task_identifier 3. Worker POSTs -> ${KNATIVE_SERVICE_URL}/${task_identifier} with JSON payload 4. Function executes -> returns success/failure 5. Worker updates -> marks job as complete or failed (retries up to max_attempts)

Headers sent to the function:

  • X-Worker-Id — worker instance identifier
  • X-Job-Id — job row ID
  • X-Database-Id — database context (nullable)
  • X-Actor-Id — user who triggered the job (nullable)

Key Environment Variables

VariableDescription
KNATIVE_SERVICE_URLBase URL for Knative functions
JOBS_SCHEMASchema name (default: app_jobs)
JOBS_SUPPORT_ANYAccept all task types (true/false)
JOBS_SUPPORTEDComma-separated task list (when JOBS_SUPPORT_ANY=false)

Scheduled Jobs

For recurring jobs, use app_jobs.add_scheduled_job() or the runtime_schedules table (in agentic-db):

-- database_id and actor_id are read from JWT claims automatically
SELECT app_jobs.add_scheduled_job(
  identifier := 'daily_report',
  payload := '{"report_type": "daily"}'::json,
  schedule_info := json_build_object(
    'rule', '0 9 * * *'  -- 9 AM daily
  )
);

The scheduler component in knative-job-service evaluates cron expressions and enqueues jobs at the appropriate times.

References

FileContent
common-patterns.mdProcess wrappers and common job patterns
payload-strategies.mdPayload strategies for job triggers

Cross-References

  • Cloud functions (Knative handlers): `constructive-platform`
  • Security policies: `constructive-security`
  • AI and embeddings: `constructive-agents`
  • Events (shared conditions system): `constructive-events`
  • Blueprint definition format: `constructive-blueprints`

Related skills

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.