Releases: satya-anguluri/capstead
Release list
v0.8.0 - Execution subtrees
Walk a whole capability execution tree in one call, and get the actual tree from the actuator instead of one level of it.
⚠️ Breaking
GET /actuator/capabilityexecutions/{id}now nests to full depth.childrenholds trees rather than executions:
-{ "execution": { … }, "children": [ { … }, { … } ] }
+{ "execution": { … }, "children": [ { "execution": { … }, "children": [ … ] } ] }A consumer reading children[].executionId must read children[].execution.executionId. An unknown or aged-out id still returns null.
The type has always been called ExecutionTree and previously returned an execution plus its direct children — so a capability that called a capability that called another reported the middle layer and stopped, and a caller who did not know to walk it received a complete-looking answer missing everything below depth one.
CapabilityExecutionQuerygainedsubtree(String). Third-party implementations of that interface need the new method; both bundled implementations have it.
Added
-
subtree(executionId)onCapabilityExecutionQuery— the root and every descendant in one call.childrenOfanswers a single level, so reassembling a tree cost a round trip per level and left every caller writing the same traversal. In-memory: one pass to index by parent, then breadth-first. Over JDBC: a recursive CTE, exercised against H2 and MySQL 8. -
Ordering is part of the contract. Root first, every node after its own parent, so a consumer builds the nested shape in a single pass with no sorting. Siblings are most-recent-first, tie-broken on execution id so that calls recorded in the same millisecond — a capability fanning out to several tools does exactly that — come back in a fixed order. Both implementations produce the same order.
-
Malformed graphs are bounded, not fatal. Nothing validates that parent links form a tree, so a record whose ancestor claims it as a parent would loop forever on a read path serving an actuator endpoint. The in-memory store tracks visited ids, the CTE bounds depth, and each returns a truncated tree rather than hanging. A duplicated id cannot detach already-attached children from the response.
-
A build.
mvn verifynow runs on every pull request and push tomain, on a runner with Docker so the MySQL Testcontainers round-trips actually execute — they are@Testcontainers(disabledWithoutDocker = true)and skip without a daemon, which meant a green local run could be tests that never ran. The build reports totals and names any suite that skipped.
Fixed
- Test isolation in
JdbcCapabilityExecutionRoundTripTest— every test opened the same H2 database under its default name and none closed it, so rows leaked between tests. The kind of fault that passes alone and fails together.
Example
// Every descendant, ordered root-first, parents before children.
List<CapabilityExecution> tree = query.subtree(rootExecutionId);GET /actuator/capabilityexecutions/{id}
{
"execution": { "capabilityName": "Generate Course", "executionId": "exec-1", … },
"children": [
{ "execution": { "capabilityName": "Generate Lesson", … },
"children": [ { "execution": { "capabilityName": "Score Lesson", … }, "children": [] } ] }
]
}
Not in this release
subtree returns the same CapabilityExecution records the rest of the query interface returns rather than a new export type. An export shape that consumers persist and diff is a contract worth settling once, alongside the ordered execution events it will need to carry — so findByAttribute and a versioned export shape remain open.
io.capstead:capstead-starter:0.8.0 · CHANGELOG · Maven Central
v0.7.0 - Config-declared usage metering
Meter and price non-token-billed model calls (TTS characters, transcription seconds, per-request APIs) with zero client-code changes.
- New
capstead.capabilities[].usageblock:model,unit(tokens|characters|seconds|requests),input-from-arg CapabilityUsageRule(runtime): the interceptor synthesizes oneModelInvocationfrom the declared argument when the call records nothing itself; real enrichment always wins- Priced through the existing
capstead.costestimator like any token-billed model - Fix:
capstead.cost.modelsnow accepts input-only (or output-only) rates — required for usage-metered models
Example:
capstead:
capabilities:
- name: "Synthesize Speech"
bean: elevenLabsSpeechSynthesizer
method: synthesizeMp3
usage:
model: elevenlabs/eleven_multilingual_v2
unit: characters
input-from-arg: 0
cost:
models:
"[elevenlabs/eleven_multilingual_v2]":
input-per-million-tokens: 220v0.6.0
Config-declared pipelines
New capstead.pipelines property lets applications describe a multi-step workflow purely in configuration — no client code changes, no schema changes. Runs are assembled at read time from already-recorded root executions (ordered step matching with a bounded max-gap).
- New
/actuator/capabilitypipelinesendpoint: pipeline scorecards + per-run drill-down (wall time, tokens, cost, per-step breakdown) - Dashboard now shows a Pipelines section with run detail view
PipelineDefinition/PipelineRun/PipelineScorecard/PipelineAssemblerin capstead-core
Fixes
- Registry and metadata-resolver beans are now static
ROLE_INFRASTRUCTUREbeans, silencing the 'not eligible for getting processed by all BeanPostProcessors' startup warning in client apps.
Example:
capstead:
pipelines:
- name: Generate Lesson Pipeline
domain: EngineerPrep
owner: Content Team
max-gap: 5m
steps:
- Author Lesson
- Write Episode
- Synthesize Episode Audio
- Generate Assessments
- Generate GitHub Repo0.5.1 — Provider-neutral declarative capabilities + dashboard browse
Capstead 0.5.1
Declarative capabilities are now provider-neutral, and the dashboard is browsable even before any executions.
✨ Provider-neutral declarative capabilities
@CapabilityClient no longer requires Spring AI. Capstead renders prompts, resolves model profiles, binds structured output, and governs the call — and delegates only the model call to a one-method SPI you implement (or the Spring AI default):
@Bean
CapabilityModelInvoker modelInvoker(MyLlmClient llm) { // LangChain4j, an SDK, raw HTTP…
return req -> llm.complete(req.model(), req.systemPrompt(), req.userPrompt());
}- New SPI in
capstead-runtime:CapabilityModelInvoker+CapabilityModelRequest. - The declarative engine moved into
capstead-starter(no Spring AI dependency). capstead-spring-ainow provides the default Spring AIChatClient-backed invoker — Spring AI users need no bean.- Structured output is bound provider-neutrally (JSON → your return type).
Full guide: docs/DECLARATIVE-CAPABILITIES.md.
🖥️ Dashboard: browse registered capabilities with no executions
The dashboard now merges the catalog with scorecards, so every registered @Capability is listed even before it runs — unexecuted capabilities show a "No runs" badge and are still clickable to their metadata (owner, domain, version, tags). Fixes the dead-end where clicking a domain with no recorded executions showed a blank page.
📦 Get it
<dependency>
<groupId>io.capstead</groupId>
<artifactId>capstead-starter</artifactId>
<version>0.5.1</version>
</dependency>
<!-- Optional: default Spring AI ChatClient invoker for declarative capabilities -->
<dependency>
<groupId>io.capstead</groupId>
<artifactId>capstead-spring-ai</artifactId>
<version>0.5.1</version>
</dependency>Runnable example (no API keys, no Spring AI): samples/.
0.4.0 — Declarative capabilities
Capstead 0.4.0
Declarative capabilities — Capstead can now write and govern your AI capability, not just observe a hand-written one.
✨ Highlights
Declarative capabilities (@CapabilityClient)
Annotate a bodyless interface method with a prompt; Capstead renders it, routes the model, calls Spring AI's ChatClient, and binds the response to your return type — while the existing @Capability advisor governs it (recording, cost, budgets, execution tree, dashboard, catalog).
@CapabilityClient
@ModelProfile("reasoning")
public interface LessonCapability {
@Capability(name = "Generate Lesson", domain = "Learning")
@Prompt("Generate a Java lesson for {{topic}}")
Lesson execute(String topic); // no body — Capstead writes it
}Model routing is config-driven, so capability code never names a model:
capstead:
ai:
profiles:
reasoning: { model: us.anthropic.claude-sonnet-4-6, temperature: 0.2 }New annotations: @CapabilityClient, @Prompt, @SystemPrompt, @ModelProfile, @P. Requires capstead-spring-ai + Spring AI's ChatClient. Full guide: docs/DECLARATIVE-CAPABILITIES.md.
Dashboard: domain grouping
Capabilities are now grouped into domain cards (from the catalog), with click-to-filter — on top of the Models column and execution drill-down added in 0.3.3.
🔧 Notes
- Declarative capabilities appear in
/actuator/capabilities, on the dashboard, and in domain grouping exactly like annotation- and config-declared ones. - No breaking changes to existing APIs.
📦 Get it
<dependency>
<groupId>io.capstead</groupId>
<artifactId>capstead-starter</artifactId>
<version>0.4.0</version>
</dependency>
<!-- declarative capabilities also need: -->
<dependency>
<groupId>io.capstead</groupId>
<artifactId>capstead-spring-ai</artifactId>
<version>0.4.0</version>
</dependency>Runnable example (no API keys): samples/.
Capstead 0.3.2 — durable, cross-instance capability dashboard
Capstead — governance & observability for your AI capabilities in Spring Boot, from one annotation. It sits around Spring AI / LangChain4j, not against them.
What's in 0.3.2
The 0.3.x line turns @Capability from static metadata into a durable execution recorder. 0.3.2 completes it:
- Durable dashboard. With
capstead-jdbcenabled,/actuator/capabilityscorecard,/actuator/capabilityexecutions, and the/capsteaddashboard now read the durable store — metrics survive restarts and aggregate across instances, not just the one that served the request. - MySQL support (since 0.3.1) —
capstead-jdbcapplies a vendor-appropriate schema (PostgreSQL, MySQL, H2). - Per-model invocations, parent-child execution trees, recording modes, privacy controls (since 0.3.0).
Use 0.3.2. It supersedes 0.3.0 (an actuator serialization bug) and 0.3.1 (dashboard read only the in-memory store).
Install
<dependency>
<groupId>io.capstead</groupId>
<artifactId>capstead-starter</artifactId>
<version>0.3.2</version>
</dependency>Durable, cross-instance history:
capstead:
jdbc:
enabled: true
retention-days: 90Running in production at engineerprep.io; clone-and-run demo in
samples/. All eight modules are
published as io.capstead:*:0.3.2 on Maven Central.
Capstead 0.3.1 — durable execution recorder
Capstead turns ordinary Spring Boot methods into governed, versioned, observable business capabilities — the governance layer around Spring AI / LangChain4j, not another AI framework.
Spring AI tells you about a model call. Capstead tells you about the business capability.
0.3.x — the durable execution recorder
@Capability is no longer just static metadata. Capstead now records every execution over time:
- Per-model invocations — a capability that calls the model several times (retries, multi-step, fan-out) captures each call, so token usage and cost are attributed per model, not just per capability.
- Parent-child execution trees — nested
@Capabilitycalls are linked automatically;GET /actuator/capabilityexecutions/{id}returns the tree. - Durable, cross-instance persistence — add
capstead-jdbcto persist executions to your database (PostgreSQL, MySQL, H2). Scorecards and history survive restarts and aggregate across instances. Capstead creates and owns its tables. - Recording modes —
capstead.executions.recording-mode: best-effort | sync | async(recording never fails your business call). - Privacy by default — inputs/outputs are not stored unless you opt in; pluggable
CapabilityDataRedactor+CapabilityPrincipalProvider. - New endpoint —
GET /actuator/capabilityexecutions(+/{id}for the tree).
Capabilities can be declared by annotation or YAML config (capstead.capabilities), and the two styles coexist.
Install
<dependency>
<groupId>io.capstead</groupId>
<artifactId>capstead-starter</artifactId>
<version>0.3.1</version>
</dependency>Notes
- Use
0.3.1. It supersedes0.3.0, which had a serialization bug that returned HTTP 500 from/actuator/capabilityexecutionsand/actuator/capabilityscorecard/{name}once they held data. 0.3.1adds MySQL support tocapstead-jdbc(vendor-aware schema).
Try it: a clone-and-run demo lives in samples/. Capstead runs in production at engineerprep.io.
All eight modules — capstead-annotations, capstead-core, capstead-runtime, capstead-starter, capstead-spring-ai, capstead-mcp, capstead-mcp-server, capstead-jdbc — are published as io.capstead:*:0.3.1 on Maven Central.