This guide explains how to embed the cogg.cpp transition kernel into your own C++ application.
While cogg.cpp provides a built-in cogg-cli and a llama.cpp backend out of the box, its true power lies in its embeddable API. You can use it to give any AI model (local or API-based) a persistent, crash-resilient lifecycle and an explicit logical clock of committed transitions.
To run a durable agent, you need three components:
cogg::Store: The SQLite database that safely handles atomic commits, admission quotas, and the agent's inbox.cogg::Backend: The interface that wraps your model logic. It receives the current state (Present) and returns a decision (Proposal).cogg::Runtime: The orchestrator that steps time forward, checking time limits and coordinating the Store and Backend.
To plug your model (or custom logic) into the kernel, inherit from cogg::Backend. The kernel will call propose() whenever the agent is allowed to wake up.
#include <cogg/kernel.hpp>
#include <iostream>
#include <thread>
#include <chrono>
class MyAgentBackend : public cogg::Backend {
public:
std::string name() const override { return "my_custom_backend"; }
cogg::Proposal propose(const cogg::Present& present) override {
std::cout << "Agent woke up! Tick: " << present.state.tick << "\n";
// 1. Read the state (memory, inbox event, etc.)
if (present.occasion.kind == "external") {
std::cout << "Received: " << present.occasion.payload["text"] << "\n";
}
// 2. Formulate a proposal (what happens next)
cogg::Proposal prop;
prop.kind = "reflection";
// Reflection carries no outward text; speech is a separate transition kind.
// 3. Write to memory (atomic key-value update)
prop.memory.push_back({"last_thought", "I am processing the input."});
// 4. Request the next autonomous wake-up (Endogenous Time)
// e.g., Request to wake after 5 seconds (subject to admission limits) (5000 ms)
prop.wake_after_ms = 5000;
return prop;
}
};Now, let's wire it up in main().
int main() {
// Run with a fresh my_agent.db. create() intentionally rejects duplicate subjects.
// 1. Initialize the SQLite Store
cogg::Store store("my_agent.db");
// 2. Define the agent's time and admission limits
cogg::Limits limits;
limits.min_wake_ms = 1000; // Cannot wake up faster than 1s
limits.max_attempts = 10; // Max 10 wakes per period
limits.period_ms = 60000; // The period is 60s (10 wakes / minute)
// 3. Create the agent (subject)
store.create("agent_01", limits, cogg::wall_now());
// 4. Send an external event to the inbox
store.submit(
"agent_01", // Subject ID
"msg_123", // Unique Idempotency Key
cogg::json{{"text", "Hello!"}},
cogg::wall_now()
);
// 5. Initialize Backend and Runtime
MyAgentBackend backend;
cogg::Runtime runtime(store, backend, cogg::wall_now);
// 6. Process the initial created occasion first. The queued message follows.
runtime.step("agent_01", cogg::wall_now());
std::this_thread::sleep_for(std::chrono::milliseconds(limits.min_wake_ms));
// Step again to process the external message
// The Runtime checks quotas, reads the inbox, calls backend.propose(),
// and commits the result atomically to SQLite.
auto snapshot = runtime.step("agent_01", cogg::wall_now());
if (snapshot) {
std::cout << "Success! New Tick: " << snapshot->tick << "\n";
std::cout << "Scheduled Wake: " << snapshot->wake_at.value_or(0) << "\n";
}
store.verify("agent_01");
return 0;
}When runtime.step() is called:
- Time Check: The
Storechecked ifagent_01was eligible to wake up according to itsLimits. - Execution: The
Runtimepassed thePresentstate (containing theHello!event) to yourMyAgentBackend. - Persistence: The
Proposal(including the 5-second sleep request and the memory write) was cryptographically hashed and saved to SQLite in a single transaction.
Because cogg.cpp is fail-closed, if someone pulled the server's power cord during propose(), the event remains safe in the inbox. When the server reboots, calling runtime.step() can retry the operation after reopening the existing database.
The first step handles created; the second handles external. Minimum spacing
applies to both. Run this example in a fresh directory; repeating create() on
an existing subject is an error. A reflection has empty text; use speech
for outward text. Recovery preserves committed state, while interrupted inference
may run again.
See request recovery and memory budgets for targeted request receipts, paginated history, explicit retrieval queries and maintenance of a history whose open tasks exceed an executor’s memory budget.