Attention
This project is still in an early phase of development.
The python API is not yet stable, and some aspects of the schema for the blueprint and workplan will likely evolve. Therefore whilst you are welcome to try out using the package, we cannot yet guarantee backwards compatibility. We expect to reach a more stable version in 2026.
Custom C-Star Applications#
Custom applications enable users to execute new types of behavior with C-Star. The real power of custom applications becomes clear when they are integrated into a workplan.
Applications Overview#
Creating an application requires three key components
Implement a Blueprint for your application. Use it to expose any configuration options available for your application.
Implement a BlueprintRunner for your application. A runner instance will be created by C-Star when asked to execute the associated Blueprint.
Tie the blueprint and runner together by registering an ApplicationDefinition.
Creating a Blueprint#
A Blueprint (see: cstar.orchestration.models.Blueprint) serves
as the interface for users to execute your application. Instead of
writing code, users will create a file containing a configured Blueprint.
Your Blueprint may include as many configuration options as needed.
For illustration purposes, consider creating an application that will be send notifications. We create a HelloWorldBlueprint as follows:
from cstar.orchestration.models import Blueprint
class HelloWorldBlueprint(Blueprint):
"""A simple blueprint demonstrating the integration of a Blueprint and it's
runner application.
"""
application: str = "hello_world"
"""A unique identifier for this application."""
target: str
"""The person to notify."""
Notice how this blueprint contains no execution logic - only the configuration necessary to send the notification.
Note
Implementing HelloWorldBlueprint requires the application field to be a unique string. It is used by C-Star to manage the lifecycle of the application.
HelloWorldBlueprint is functionally complete but under the hood blueprints rely on pydantic to handle model serialization and deserialization. Adding additional fields to this blueprint can make use of all the power of pydantic, like adding field constraints (e.g. min_length=10) or more complex behaviors with model or field validators. See the pydantic documentation for more info.
Creating a BlueprintRunner#
A BlueprintRunner (see: cstar.entrypoint.runner.BlueprintRunner) is
used to execute an application. At runtime, the runner receives
a Blueprint instance, configures it’s behavior using the blueprint, and performs any
behaviors desired by the application author.
Note
The real power of Blueprint and BlueprintRunner are clear when we go on to create a Workplan.
While not covered here, a Workplan enables many applications to be executed together, and even allows us to re-use Blueprint files by asking C-Star to override values at runtime.
The simplest BlueprintRunner can be completed in a single method. Here, we create a runner for the HelloWorldBlueprint that uses print to show our notification in the console.
When implementing run, we can perform any action: call external services, create
files, etc. C-Star requires the application developer to ensure that the runner
status is updated whenever it completes or fails. See cstar.execution.handler.ExecutionStatus
for additional details on the available states.
In this example, we:
send our notification
update the runner status with self.add_state(ExecutionStatus.COMPLETED)
return the result to the calling code with return self.result
from cstar.entrypoint.runner import BlueprintRunner
from cstar.applications.core import RunnerResult
class HelloWorldRunner(BlueprintRunner[HelloWorldBlueprint]):
"""Worker class to execute a simple "Hello, world" application specified via blueprint."""
@t.override
async def run(self) -> RunnerResult[HelloWorldBlueprint]:
"""Process the blueprint.
Returns
-------
RunnerResult
The result after completing processing of the blueprint.
"""
print(f"Hello, {self.blueprint.target}")
self.add_state(ExecutionStatus.COMPLETED)
return self.result
Creating the ApplicationDefinition#
An application definition (see: cstar.applications.core.ApplicationDefinition) links
our components together. It also let’s us configurate additional,
advanceed behaviors, like:
Specifying Transforms to modify values in the blueprint at runtime
Specifying migration Adapters for upgrading blueprints as the schema changes
For our sample application, we create HelloWorldApplication, specifying our unique application identifier and the previously created HelloWorldBlueprint and HelloWorldRunner:
from cstar.applications.core import ApplicationDefinition, register_application
@register_application
class HelloWorldApplication(
ApplicationDefinition[HelloWorldBlueprint, HelloWorldRunner],
):
name = "hello_world"
runner = HelloWorldRunner
blueprint = HelloWorldBlueprint
Tying it Together#
After completing our components, we still need to create an instance of the blueprint. First, I configure a HelloWorldBlueprint and save it to a file.
name: notify @ankona
description: Send a notification to @ankona
application: hello_world
target: '@ankona'
Notice the additional fields that are included from the Blueprint base class:
name - a user-friendly name used for logs and tracking in the system
description - a user-friendly description of the purpose of this blueprint instance
These fields are required when creating the configured blueprint instance. For example, we might later create another file notify-scott.yaml with:
name: notify @scott
description: Send a notification to @scott
application: hello_world
target: '@scott'
Remember - a Blueprint defines the available configuration. The blueprint files specify how an execution of the application should behave.
Executing the Blueprint#
We’ve created our blueprint and runner classes and created two separate blueprint instances. We have finally reached the point where we can execute the application using the C-Star CLI:
> cstar blueprint run notify-ankona.yaml
Hello, @ankona
> cstar blueprint run notify-scott.yaml
Hello, @scott