Skip to content

Releases: satya-anguluri/capstead

v0.8.0 - Execution subtrees

Choose a tag to compare

@satya-anguluri satya-anguluri released this 04 Sep 05:17
ed6b546

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. children holds 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.

  • CapabilityExecutionQuery gained subtree(String). Third-party implementations of that interface need the new method; both bundled implementations have it.

Added

  • subtree(executionId) on CapabilityExecutionQuery — the root and every descendant in one call. childrenOf answers 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 verify now runs on every pull request and push to main, 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

Choose a tag to compare

@satya-anguluri satya-anguluri released this 21 Jul 02:38

Meter and price non-token-billed model calls (TTS characters, transcription seconds, per-request APIs) with zero client-code changes.

  • New capstead.capabilities[].usage block: model, unit (tokens | characters | seconds | requests), input-from-arg
  • CapabilityUsageRule (runtime): the interceptor synthesizes one ModelInvocation from the declared argument when the call records nothing itself; real enrichment always wins
  • Priced through the existing capstead.cost estimator like any token-billed model
  • Fix: capstead.cost.models now 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: 220

v0.6.0

Choose a tag to compare

@satya-anguluri satya-anguluri released this 20 Jul 04:22

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/capabilitypipelines endpoint: 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 / PipelineAssembler in capstead-core

Fixes

  • Registry and metadata-resolver beans are now static ROLE_INFRASTRUCTURE beans, 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 Repo

0.5.1 — Provider-neutral declarative capabilities + dashboard browse

Choose a tag to compare

@satya-anguluri satya-anguluri released this 13 Jul 13:14

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-ai now provides the default Spring AI ChatClient-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

Choose a tag to compare

@satya-anguluri satya-anguluri released this 13 Jul 04:31

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

Choose a tag to compare

@satya-anguluri satya-anguluri released this 13 Jul 01:59

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-jdbc enabled, /actuator/capabilityscorecard, /actuator/capabilityexecutions, and the /capstead dashboard 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-jdbc applies 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: 90

Running 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

Choose a tag to compare

@satya-anguluri satya-anguluri released this 13 Jul 01:27

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 @Capability calls are linked automatically; GET /actuator/capabilityexecutions/{id} returns the tree.
  • Durable, cross-instance persistence — add capstead-jdbc to persist executions to your database (PostgreSQL, MySQL, H2). Scorecards and history survive restarts and aggregate across instances. Capstead creates and owns its tables.
  • Recording modescapstead.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 endpointGET /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 supersedes 0.3.0, which had a serialization bug that returned HTTP 500 from /actuator/capabilityexecutions and /actuator/capabilityscorecard/{name} once they held data.
  • 0.3.1 adds MySQL support to capstead-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.