Skip to main content

Hooks and Action Blocks

Hooks and action blocks are the event-driven layer of Codebolt's memory system. Hooks watch for lifecycle events and fire automatically. Action blocks are the modular code units that hooks (and ingestion pipelines) execute. Together they let you build memory automation that runs without an agent prompt.

Action blocks

An action block is a self-contained code module with a defined entry point. It connects to the Codebolt server over WebSocket like an agent, but it executes a specific task and returns — it doesn't run a full LLM loop.

Action blocks are the recommended way to encapsulate custom memory logic that:

  • needs to be reused across multiple hooks or ingestion pipelines
  • is too complex for inline code
  • needs its own configuration, dependencies, or tests

Where action blocks live

SourceLocationScope
Project.codebolt/actionblocks/<name>/Available only to this project
Global~/.codebolt/actionblocks/<name>/Available to all projects on this machine
Built-inProvided by CodeboltAlways available

Structure

.codebolt/actionblocks/memory-summarizer/
├── actionblock.yaml ← manifest
├── index.js ← entry point
└── package.json

actionblock.yaml:

name: memory-summarizer
description: Summarizes episodic memory into a markdown note
version: 1.0.0
entryPoint: index.js
metadata:
author: your-name
tags: [memory, summarization]
inputs:
- name: memoryId
type: string
required: true
- name: maxTokens
type: number
required: false
outputs:
- name: noteId
type: string

index.js:

import codebolt from '@codebolt/codeboltjs';

codebolt.app.onMessage(async (message) => {
const { memoryId, maxTokens = 2000 } = message.params;

// Query the episodic memory
const { events } = await codebolt.episodicMemory.queryEvents({
memoryId,
filters: { lastMinutes: 60 }
});

// Summarize with the LLM
const summary = await codebolt.llm.complete({
messages: [
{ role: 'user', content: `Summarise these events into a concise markdown note:\n${JSON.stringify(events)}` }
],
maxTokens
});

// Write to markdown memory
const note = await codebolt.memory.markdown.save(summary, { source: memoryId });

codebolt.app.sendMessage({ noteId: note.id });
});

Calling an action block from an agent

const result = await codebolt.actionBlock.execute({
actionBlockId: 'memory-summarizer',
params: { memoryId: 'run-2025-04-22', maxTokens: 1500 },
threadId: currentThreadId
});

console.log(result.noteId); // ID of the created note

Hooks

Hooks listen for lifecycle events and automatically run an action when one fires. They are JSON files stored in .codebolt/hooks/ with a .hook extension.

Trigger types

TriggerFires when…
fileCreatedA file is created in the project
fileUpdatedA file is modified
fileDeletedA file is deleted
gitCommitA commit is made
gitPushCode is pushed to a remote
threadCreatedA new chat thread is opened
threadCompletedA chat thread ends
agentStartedAn agent begins execution
agentCompletedAn agent finishes

Hook structure

{
"name": "summarize-on-thread-end",
"description": "Summarize the conversation when a thread completes",
"enabled": true,
"version": "1.0.0",
"when": {
"type": "threadCompleted",
"creatorFilter": "any",
"agentTypeFilter": "main"
},
"then": {
"type": "runActionBlock",
"actionBlockId": "memory-summarizer"
}
}

Action types

Action typeWhat it does
runAgentLaunches an agent with an instruction
runActionBlockExecutes an action block
runCommandRuns a shell command
sendToGatewayRoutes a message through the chat gateway

runAgent

{
"then": {
"type": "runAgent",
"agentId": "memory-optimizer-agent",
"instruction": "Compact episodic memory for the completed thread.",
"runInSameThread": false
}
}

runActionBlock

{
"then": {
"type": "runActionBlock",
"actionBlockId": "memory-summarizer"
}
}

runCommand

{
"then": {
"type": "runCommand",
"command": "node .codebolt/scripts/prune-old-vectors.js"
}
}

Filtering triggers

File pattern filter (only fire when specific files change):

{
"when": {
"type": "fileUpdated",
"patterns": ["src/services/**/*.ts", "src/models/**/*.ts"]
},
"then": {
"type": "runActionBlock",
"actionBlockId": "code-entity-extractor"
}
}

Git branch filter (only fire on specific branches):

{
"when": {
"type": "gitCommit",
"branches": ["main", "release/*"]
}
}

Creator filter (only fire when a human, not an agent, takes the action):

{
"when": {
"type": "fileUpdated",
"creatorFilter": "user"
}
}

Agent type filter (only fire for main agents, not sub-agents):

{
"when": {
"type": "agentCompleted",
"agentTypeFilter": "main"
}
}

Memory-focused hook patterns

Embed code changes on commit

{
"name": "embed-code-on-commit",
"enabled": true,
"when": {
"type": "gitCommit",
"creatorFilter": "any"
},
"then": {
"type": "runActionBlock",
"actionBlockId": "diff-embedder"
}
}

The diff-embedder action block reads the git diff, chunks it, and writes embeddings to the vector store — so future agents can recall "how did we implement X?" semantically.

Summarize episodic memory at session end

{
"name": "session-memory-summary",
"enabled": true,
"when": {
"type": "threadCompleted",
"agentTypeFilter": "main"
},
"then": {
"type": "runActionBlock",
"actionBlockId": "memory-summarizer"
}
}

Update knowledge graph on source file change

{
"name": "sync-kg-on-service-change",
"enabled": true,
"when": {
"type": "fileUpdated",
"patterns": ["src/services/**/*.ts"],
"creatorFilter": "any"
},
"then": {
"type": "runActionBlock",
"actionBlockId": "ts-entity-extractor"
}
}

Managing hooks via the SDK

Hooks can also be created and managed programmatically from an agent:

// List all hooks
const hooks = await codebolt.hooks.list();

// Create a hook
await codebolt.hooks.create({
name: 'prune-on-push',
enabled: true,
when: { type: 'gitPush' },
then: { type: 'runActionBlock', actionBlockId: 'vector-pruner' }
});

// Enable / disable
await codebolt.hooks.enable('prune-on-push');
await codebolt.hooks.disable('prune-on-push');

Hook → action block → memory: the full flow

lifecycle event fires

HookManager matches event to hook(s)

HookExecutor runs the action

├─ runActionBlock → ActionBlock executes
│ ↓ reads/writes via SDK
│ └─ memory store updated

├─ runAgent → Agent runs with instruction
│ ↓ may call memoryIngestion.execute() etc.
│ └─ memory store updated

└─ runCommand → shell script executes
↓ may call Codebolt CLI
└─ memory store updated

This flow means memory is kept current without any user prompt. Agents that start a new session find the knowledge graph, vector store, and KV state already reflecting the latest project activity.

See also