Skip to content

Make your first capsule

You will build a capsule called scale that multiplies each integer it receives by a configured factor, then wire it between a simulator and a logger and run the result. The finished code is in the repository: the capsule in capsules/scale/scale_capsule.c and the project in examples/first_capsule/.

You need CMake 3.14 or newer, a C11 compiler and pthreads, on Linux or macOS.

The SIG_CAPSULES option lists the capsules compiled into the host. Setting it replaces the default list, so name every capsule you need: the simulator produces values, scale changes them, and the logger prints them.

Terminal window
cmake -S . -B build -DSIG_CAPSULES="counter;simulator;logger;scale"
cmake --build build

A capsule is a sig_capsule value: a name, tables of input and output terminals, and a table of the properties it accepts. scale has one property, factor, an integer that defaults to 2 and must be at least 1.

enum { SCALE_FACTOR };
static const sig_property scale_properties[] = {
[SCALE_FACTOR] = SIG_PROP_INT_MIN("factor", 2, 1, "Number each incoming int is multiplied by"),
};

The input terminal’s handler runs on the service’s worker thread for each signal. It reads the incoming value, multiplies it by the property and sends the result on the output terminal. A value that is not an int, or whose product does not fit in an int, is dropped with a warning.

static sig_status_t scale_signal(sig_capsule_instance *instance, const sig_value *signal,
const char *terminal_name) {
sig_int_t n;
sig_int_t product;
sig_value scaled;
(void) terminal_name;
if (sig_value_get_int(signal, &n) != SIG_OK) {
sig_log_warn("scale %s: dropping a signal that is not an int", instance->name);
return SIG_OK;
}
if (__builtin_mul_overflow(n, sig_capsule_instance_int(instance, SCALE_FACTOR), &product)) {
sig_log_warn("scale %s: dropping a signal whose scaled value overflows", instance->name);
return SIG_OK;
}
scaled = sig_value_int(product);
return sig_capsule_instance_notify(instance, SIG_TERMINAL_DEFAULT, &scaled);
}

Finally the capsule itself ties the pieces together: one default input terminal wired to the handler, one default output terminal, and the property table.

const sig_capsule scale_capsule = {
"scale",
1,
scale_input_terminals,
1,
scale_output_terminals,
NULL,
NULL,
sizeof(scale_properties) / sizeof(scale_properties[0]),
scale_properties,
.category = "flow",
};

Writing capsules covers terminals, properties, start and stop hooks and scheduled work in full.

The host only knows the capsules compiled into it. The repository already has scale registered; for a capsule of your own, do the same three things:

  1. Put it in capsules/<name>/<name>_capsule.c with a header that declares extern const sig_capsule <name>_capsule;. Here that is capsules/scale/scale_capsule.h.
  2. Add <name> to the SIG_CAPSULES list, as in step 1.
  3. Add a block to capsules/sig_builtin_capsules.c that includes the header and registers the capsule. For scale the block is:
#ifdef SIG_CAPSULE_SCALE
status = sig_capsule_registry_register(reg, &scale_capsule);
if (status != SIG_OK) {
return status;
}
#endif

with a matching #include "scale_capsule.h" under #ifdef SIG_CAPSULE_SCALE near the top of the file.

A project is a directory with a configurations/ folder and a services/ folder. Each configuration names a capsule type and sets its properties. The file name is the configuration name.

configurations/Sim.cfg is a simulator that counts from 0 to 10 and then wraps back to the start, one value every 500 ms:

{"type":"simulator","attr_value":{"end":10,"interval_ms":500}}

configurations/Scale.cfg is a scale capsule with a factor of 10:

{"type":"scale","attr_value":{"factor":10}}

configurations/Log.cfg is a logger:

{"type":"logger"}

services/first.cfg is the service. It starts automatically and lists the three configurations as instances. Each instance’s receivers connect an output terminal to an input terminal of another instance: Sim feeds Scale, and Scale feeds Log.

{"auto_start":true,"execution":[{"name":"Sim","receivers":{"__default_terminal_value":[{"input":"__default_terminal_value","name":"Scale"}]}},{"name":"Scale","receivers":{"__default_terminal_value":[{"input":"__default_terminal_value","name":"Log"}]}},{"name":"Log"}]}

Configuration describes every field.

sighost loads the project and runs it until it receives SIGINT or SIGTERM. Its default log level is trace, which also prints a line for every signal, so this run uses -l info:

Terminal window
build/host/posix/sighost -p examples/first_capsule -l info

This is the output of a run that was stopped with SIGTERM after about three seconds. The simulator emits 0, 1, 2 and so on; scale multiplies each by 10; the logger prints the result. Timestamps will differ.

2026-10-26T16:05:47.836Z INFO host/sig_host.c:53: sig_host: opened file store "examples/first_capsule"
2026-10-26T16:05:47.837Z INFO src/runtime/sig_runtime.c:393: runtime: starting (project in the 'file' store, 0 module(s))
2026-10-26T16:05:47.837Z INFO src/project/sig_project.c:419: loading project from the 'file' store
2026-10-26T16:05:47.838Z INFO src/project/sig_project.c:448: loaded project: 3 capsule(s), 1 service(s)
2026-10-26T16:05:47.838Z INFO src/runtime/sig_service.c:662: service 'first' started (3 instances)
2026-10-26T16:05:47.838Z INFO src/project/sig_project.c:513: auto-start complete: 1 service(s) started
2026-10-26T16:05:47.838Z INFO src/runtime/sig_runtime.c:437: runtime: running
2026-10-26T16:05:48.344Z INFO capsules/logger/logger_capsule.c:28: logger Log: 0
2026-10-26T16:05:48.843Z INFO capsules/logger/logger_capsule.c:28: logger Log: 10
2026-10-26T16:05:49.340Z INFO capsules/logger/logger_capsule.c:28: logger Log: 20
2026-10-26T16:05:49.842Z INFO capsules/logger/logger_capsule.c:28: logger Log: 30
2026-10-26T16:05:50.343Z INFO capsules/logger/logger_capsule.c:28: logger Log: 40
2026-10-26T16:05:50.843Z INFO capsules/logger/logger_capsule.c:28: logger Log: 50
2026-10-26T16:05:51.043Z INFO host/posix/sig_host_posix.c:62: host: received signal 15, requesting shutdown
2026-10-26T16:05:51.043Z INFO src/runtime/sig_runtime.c:467: runtime: stopping
2026-10-26T16:05:51.043Z INFO src/project/sig_project.c:538: stopping all services
2026-10-26T16:05:51.043Z INFO src/runtime/sig_service.c:753: service 'first' stopped
2026-10-26T16:05:51.043Z INFO src/runtime/sig_runtime.c:490: runtime: stopped

Change factor in configurations/Scale.cfg and run again to see the logged values change.

  • Writing capsules: terminals, properties, hooks and scheduled work.
  • Management API: inspect and control a running host. Build with -DSIG_NET=ON -DSIG_HTTP_SERVER=ON -DSIG_MGMT=ON and start sighost with -m 8080 to use it over REST.
  • Examples: fan-in, fan-out, shared configurations and editing in the web UI.