Cauce

Build log

Every unit of work that landed, newest first, each one naming the commit that closed it and the limitation it left behind. You can check any of it against the repository instead of taking my word for it.

Units
42102 commits
Modules
13of 15 · 2 still empty
Tests
953green, 0 failures
Since
18 May2026 · one person
Show
Next not started

Let the audit screen show what the API now says

The verification endpoint gained four verdict states, a signature report and an anchor; the screen still renders the old two. Wiring it up also means handing the head back as the anchor for the next check.

playground

Signatures

units 39–42 · September
Breaking 18e2401 2026-09-11

A verdict with four answers instead of two

Verification now answers VALID, BROKEN, TRUNCATED or UNVERIFIABLE, carries a signature report, and writes itself into the chain it verified. Truncation is only claimed against an anchor the caller supplies — a chain cannot detect its own missing tail.

The playground screen doesn't consume the new fields yet. A verification lands in the chain one drain tick later, not in the response that produced it.

cauce-governancecauce-apiplayground
Shipped 856b355 2026-09-11

Every audit entry now carries a signature

Ed25519 over the v2 entry hash. The private key lives in process configuration and never touches the database, so a database alone is not enough to forge an entry. The key-id to public-key registry is a file read from outside the database.

The JDK can't derive the public key from the private one, so both are configured and the pair self-checks at startup. The signature coverage report stayed internal until unit 42.

cauce-governancecauce-memorycauce-api
Shipped 94de4c0 2026-09-11

Pinning down exactly what gets hashed

The id enters the preimage, timestamps serialise at fixed microsecond precision and strings normalise to NFC, so the same entry hashes the same everywhere. Older entries aren’t recomputed; the verifier branches on the scheme each row declares.

An unknown scheme was reported as a malformed entry, which is misleading. Fixed in unit 42. The preimage spec lives in Javadoc, not in docs.

cauce-governance
Decision b9b19c9 2026-09-11

Deciding how signing would work, before writing it

ADR 0003 fixes Ed25519, one key per instance held outside the database, four verdict states, and a publicly published key registry. A second commit corrects a false claim the ADR made about the vocabulary guards.

Marked "accepted, not yet implemented". Export artefact, independent verifier, external time anchoring and per-tenant keys all deferred.

docs/adr

A window into the machine

units 31–38 · August to September
Shipped 9587141 2026-09-08

Token usage, per invocation

The invocation endpoint returns the token facts the ledger recorded for that call, as an additive nullable field.

Still no per-tenant aggregation and no cost materialisation.

cauce-apicauce-orchestration
Shipped 75deab3 2026-09-08

Seven screens against the real backend

Session, conversation, tenants, agents, API keys, invocations and the audit chain, all built against the mockups and reading from the running API. A build guard fails if the audit screen drifts into verdict language.

No test harness on the frontend; its gate is a build plus manual verification. The vocabulary guard covered the audit view and its stylesheet, not the wire types — widened in unit 40.

playground
Decision 2142a32 2026-09-01

Drawing every screen before building any of them

Static HTML mockups, one file per viewport, each declaring its own contract down to focus, autofill and reduced-motion states. Specification, not code.

playground
Shipped 11c6dd3 2026-08-25

The key knows who it is

The playground reads its own identity instead of asking you to paste it, and renders the full tool trace from the structured content.

The number of rounds still isn't derivable from the wire.

playground
Shipped 73f18ca 2026-08-13

An endpoint that answers "who am I"

Key introspection, structured tool content on the message DTO, and malformed UUIDs in a body answered with 400 instead of 500.

cauce-api
Shipped d57feaa 2026-08-13

The tenant tree, and agents inside it

The three tiers rendered as a tree, with agents created in the context you’re standing in.

The workspace tenant id had to be pasted by hand, because no identity endpoint existed yet.

playground
Shipped 49adc01 2026-08-12

Watching the agent work

Send to a real agent, poll invocation state every two seconds, and see the tool loop as a foldable trace. No fake streaming — the engine is asynchronous and the interface says so.

No message-to-invocation link on the wire, so invocation meta is per view.

playground
Shipped e404672 2026-08-12

Somewhere to put the key

Shell, health probe and session screen. The API key lives in memory only and is never written anywhere.

The dev proxy stands in for CORS, which the backend still doesn't configure.

playground

The chain

units 26–30 · July to August
Shipped d229793 2026-08-10

The chain can be checked from outside

One authenticated endpoint recomputes a tenant’s chain and returns the verdict, the sequence where it first breaks with a public classification, the count of pre-chain rows, and a scope block naming what recomputation does not detect.

Two verdict states only at this point, and the scope said signing was reserved — true then, superseded by unit 41.

cauce-api
Shipped c9c6f7f 2026-07-25

Administrative acts enter the chain

Creating a tenant, an agent or a key, revoking one, configuring a channel — each audited inside the transaction of the operation itself, in the subject’s chain, with the acting tenant as actor.

Actor attribution by key id deferred. Two hooks reserved without emission. The operator bootstrap is the un-audited genesis exception.

cauce-governancecauce-tenancy
Shipped ab915a3 2026-07-25

What the agent did, recorded as it happens

Message received, agent responded, invocation failed — each emitted inside the transaction of the fact it audits, so an audited event and an unrecorded one cannot come apart.

Four event types reserved without emission: external action, data access, human escalation, reply delivered.

cauce-governancecauce-core
Shipped 7f09be7 2026-07-25

Each entry carries the one before it

A hash chain per tenant, with a head row as the single source of sequence. Alter an old entry and every link after it stops matching; the verifier reports the exact sequence where it first breaks.

Signing still reserved, and with it the documented residual: a privileged attacker who recomputes the whole chain and rewrites the head is not detectable by recomputation alone.

cauce-governancecauce-memory
Shipped 848b936 2026-07-25

Capture that can't be skipped

An outbox written inside the caller’s own business transaction, and an append-only ledger where update and delete are revoked from the application’s database role. If the business fact commits, its audit record commits with it.

Chain columns created reserved and null. No real emitters yet.

cauce-governancecauce-memory

Made operable

units 22–25 · July
Shipped 82a1795 2026-07-25

A ledger of what each tenant spent

One immutable row per LLM call, written synchronously before the response event is published. If the ledger insert fails, the invocation fails.

Cost deliberately not materialised — pricing will be a versioned table and a view. No query endpoint, no aggregations.

cauce-orchestrationcauce-memory
Shipped 047bdcd 2026-07-23

Clone it and watch an agent answer

A compose profile that brings up the app and a local Ollama, pulls the model, creates a demo agent and sends the first tool-using message. The default model called the clock tool five times out of five.

The CI job only builds the image.

dockerscripts
Breaking 440bd0e 2026-07-17

Lists that don't fall over at scale

Keyset pagination over the time-ordered id, one uniform envelope across the list endpoints. A contract break from the bare arrays that came before.

The API-key listing stayed outside the contract and still returns a bare array.

cauce-apicauce-memory
Shipped b97fd5f 2026-07-17

You can ask what happened to your request

The accepted response hands back an invocation id, and an endpoint reports its state in public vocabulary decoupled from the internal lifecycle.

Reaper-abandoned invocations and non-LLM setup failures leave no message in the conversation; their only visible signal is the invocation state.

cauce-apicauce-orchestration

Out into the world

units 20–21 · July
Shipped 957cc03 2026-07-04

The reply goes back out

Outbound delivery through a dispatch port in the domain, called once the agent’s last message is committed.

Explicitly best-effort: no outbox, no retries, no deduplication on delivery.

cauce-channelscauce-core
Shipped b388e1f 2026-07-04

The first real channel

A channel SPI, a binding from a channel instance to an agent, and a Telegram inbound adapter whose ingestion is idempotent on the platform’s own update id.

The bot credential is stored in clear at rest — encryption deferred alongside per-tenant LLM credentials. Supported channels are still a hardcoded set.

cauce-channelscauce-api

Making it hold

units 17–19 · July
Shipped 1582098 2026-07-04

The first thing to listen to the events

The eight orchestration events become metrics on an authenticated actuator endpoint. The hooks laid two units earlier now have a consumer.

No exporter — no OpenTelemetry, no Prometheus, in any module.

cauce-observability
Shipped e50786e 2026-07-04

Sending the same message twice changes nothing

An optional idempotency header: a repeat with the same key for the same agent returns the original ids and ingests nothing, held by an insert-first lock on a unique constraint.

No request-body fingerprinting and no retention purge.

cauce-apicauce-orchestration
Shipped 9ad26b0 2026-07-03

The orchestrator starts saying what it did

A leaf module with a sealed contract of eight events, emitted at every step of the loop. Nothing consumes them yet — they exist so that auditing can attach later without reopening the orchestrator.

One event publishes inside the ingest transaction, so a persisting consumer will need an after-commit hook or an outbox.

cauce-orchestration-events

It stopped being a chatbot

units 14–16 · 13 June
Shipped a6f962d 2026-06-13

The loop closes

The orchestrator offers tools, runs the ones the model asks for, feeds results back and invokes again, up to ten rounds. The model calls run without holding a database connection.

The reaper timeout became a flat twelve minutes instead of a heartbeat. Tool schemas don't count against the context window.

cauce-orchestration
Shipped 5cfa65c 2026-06-13

Both providers learn to speak tools

The same neutral tool model maps onto Anthropic’s content blocks and onto the OpenAI-compatible shape. The core never learned either format.

cauce-llm-anthropiccauce-llm-openai
Shipped 279115c 2026-06-13

A way to describe a tool that belongs to no vendor

Tool definitions, calls and results as sealed types in the domain, an executable contract in a new module, and tool messages persisted with their payload. A clock tool ships built in, mostly to prove the contract end to end.

The tool registry is global — no per-agent scoping.

cauce-corecauce-toolscauce-memory

The loop goes public

units 11–13 · June
Decision 3736694 2026-06-10

Writing down what was deliberately left undone

A durable register of consciously deferred work, kept in the file the tooling reads. Known gaps stop being folklore and start being a list.

docs
Shipped 5832df4 2026-06-03

Post a message, get an answer

One endpoint resolves or opens the conversation, appends the message and queues the work in a single transaction, race-safe under a partial unique index. The reply arrives asynchronously.

The accepted response dropped the invocation id, so a client couldn't ask what happened. Fixed in unit 22.

cauce-apicauce-orchestration
Shipped 94b0ac0 2026-06-02

One adapter, three providers

A single OpenAI-compatible adapter registered as three conditional providers, with a keyless local model as the development default. The second adapter dropped in without touching the interface — the only real test an abstraction gets.

cauce-llm-openai

Who is allowed in

units 7–10 · May to June
Decision 9e77dfc 2026-06-02

Authority is the hierarchy, not a permission table

Real key authentication on every route: which tenant you are comes from the validated key, never from a header. Keys are hashed with a pepper. ADR 0002 fixes that authority is the visibility relation the database already enforces.

No roles and no scopes. Still true today.

cauce-apicauce-tenancydocs/adr
Decision 6838a14 2026-06-02

The application stops being the database owner

It now connects as a least-privilege role with row-level security live at runtime. The background worker’s cross-tenant claim goes through narrowly scoped functions, and ADR 0001 fixes those three as the only sanctioned ways out.

Not a pending item but a standing constraint: every new cross-tenant need requires a new function and a documented reason.

cauce-memorycauce-orchestrationdocs/adr
Shipped 58a37b3 2026-05-29

The first REST surface

Tenants and agents over HTTP, with a global error contract and a request id carried through the logs and back in a header.

A development header stood in for the tenant, paired with an open route. Both were explicitly marked to go away, and did, in unit 10.

cauce-api
Shipped 49145fd 2026-05-26

Keys exist before anything uses them

The key entity and the authentication filter land in the security pipeline with no endpoints behind them yet.

cauce-apicauce-core

The engine

units 5–6 · May
Shipped 73c1b3f 2026-05-26

Work leaves the request thread

A queue on the database, context assembly bounded by the model’s window, a worker that claims rows without blocking its neighbours, backoff on retry, and a reaper for claims whose worker died. Everything since stands on this.

The claim and reap cross tenants and didn't yet work under a least-privilege role. Resolved in unit 9.

cauce-orchestration
Shipped 5e9324a 2026-05-23

The core will never know which model answered

A neutral provider contract with a registry and a neutral invocation model, plus the first adapter behind it.

Streaming left outside the contract on purpose. One provider. No per-tenant credentials.

cauce-llmcauce-llm-anthropic

Isolation, in the database

units 3–4 · May
Shipped 7727733 2026-05-23

Conversations, and what closes them

Conversations and messages under the same hierarchical isolation, time-ordered ids from one place in the domain, and explicit lifecycle transitions.

Escalation was written with no caller. It still has none — it is a reserved hook, and the log says so rather than implying the feature exists.

cauce-corecauce-memory
Shipped 220b577 2026-05-21

Three tiers the database refuses to mix

Operator, partner, client — with visibility enforced by row-level security where each entity’s rule calls its parent’s. Forgetting a filter cannot leak another tenant’s rows, because the database will not return them.

The provider field was a free string validated against a hardcoded set, waiting for the contract that arrived in unit 5.

cauce-corecauce-memorycauce-tenancy

Before any code

units 1–2 · 18–21 May
Decision 28844f1 2026-05-21

Ten modules, all of them empty

The build, the local stack and continuous integration, with the shape already drawn and nothing inside it. Naming the empty modules early is what keeps the full ones from growing into each other later.

all
Decision 2494022 2026-05-20

The licence before the first line

Readme, licence and project context. Source-available now, Apache 2.0 four years after each release. No code yet, and the file said so.

repo root

First commit: 18 May 2026.