Vega SDK — Python

Core APIs

How a Provider receives an execution, uses its context, reports what happened, and remains within the public Capability contract.

Concepts

TermMeaning
ProviderLifecycle-aware implementation of a Capability's Actions.
Initialization contextRead-only configuration and services supplied once after a Provider is loaded.
Execution requestImmutable identity, inputs, optional deadline, and correlation data for one Action invocation.
Execution contextRestricted services for one invocation.
OutcomeA successful result or a structured Capability error.

Guide versus API Reference

TermMeaning
When to use a Provider hookEvery Provider method and protocol signature
How request, context, result, and error work togetherEvery field, type, default, and validation rule
How to choose progress, Artifacts, logging, or errorsComplete members of each supporting class
Common implementation patterns and boundariesExhaustive enum values and serialization details

1. Start with a Provider, not a Runtime client

BaseCapabilityProvider is the usual entry point. It represents a loaded Capability Provider; it is not a client for connecting to Vega Runtime. The Runtime calls its public lifecycle methods. Your code normally overrides four hooks:

python
class Provider(BaseCapabilityProvider):
async def on_initialize(self, context):
...
async def on_execute(self, request, context):
...
async def on_health_check(self):
...
async def on_shutdown(self):
...

Use the hooks because the base class enforces lifecycle state, retains a successful initialization context, converts unexpected execution exceptions to ProviderInternalError, returns unknown health on health-check exceptions, and cancels base-managed background tasks during shutdown. Do not connect to hardware at module import time.

2. Treat requests as immutable facts

CapabilityExecutionRequest identifies one Action execution. Its required identity fields, inputs, and metadata are deeply frozen. Use context.validate_request(request)first, then read inputs without modifying them.

python
context.validate_request(request)
text = request.inputs.get("text")
if not isinstance(text, str) or not text.strip():
raise InvalidInputError("text must be a non-empty string")
Mission, task-graph, and node fields are Runtime correlation hints — not public SDK Task or Session APIs. The SDK has no Task, Session, Event Bus, or remote streaming abstraction.

3. Use the execution context as the capability boundary

TermMeaning
Stop safelycontext.cancellation — conveys a cooperative cancellation request and reason.
Show meaningful work statecontext.progress — associates structured progress with the execution.
Record diagnostic factscontext.logger — binds structured fields and redacts secret wrappers.
Measure or compare timecontext.clock — makes time-dependent code testable.
Correlate downstream callscontext.trace — carries portable trace headers and baggage.
Produce bytes or large outputcontext.artifacts — creates portable Artifact references.
Inspect assigned resourcescontext.resource_leases — exposes read-only Runtime lease facts.
Do not treat a lease as device-level safety confirmation, and do not assume an Artifact service is present without checking for None.

4. Report progress for observable long-running work

python
await context.progress.report({"phase": "starting"})
await context.progress.report({"phase": "working", "percentComplete": 50})

The SDK freezes the payload, enforces a default 64 KiB size limit, and adds execution ID, an increasing sequence, timestamp, and trace ID. Progress is a callback into the current integration — not a WebSocket, gRPC, or general Event API.

5. Return results; raise expected failures

python
return CapabilityExecutionResult(
output={"spoken": True},
diagnostics={"backend": "mock"},
physical_termination=PhysicalTerminationStatus.NOT_APPLICABLE,
)
TermMeaning
Input shape is valid but a semantic value is unusableInvalidInputError
Device or request precondition is absentPreconditionFailedError
Required service is unavailableDependencyUnavailableError
Runtime-assigned resource is unavailableResourceUnavailableError
Device operation failsHardwareError
External API communication failsCommunicationError
Operation exceeds a Provider-observed deadlineTimeoutCapabilityError
Policy must reject workSafetyRejectionError
Provider cannot implement requested ActionUnsupportedOperationError

Do not catch these errors only to turn them into a generic failure. Let them propagate. The base Provider converts unexpected ordinary exceptions to ProviderInternalError and logs the exception type.

An error's retryable value is a hint, not an instruction to retry inside the Provider. Runtime owns retry and continuation decisions.

6. Use initialization context for stable dependencies

python
async def on_initialize(self, context):
endpoint = context.configuration.require("endpoint", str)
token = context.secrets.get("service_token")
self._client = await Client.connect(endpoint, token.get_secret_value())

The secret wrapper redacts when logged or formatted. Its raw value is still sensitive after get_secret_value; never put it in errors, diagnostics, or logs. Configuration is deeply immutable.

7. Use health as a safe operational fact

python
async def on_health_check(self):
if self._client is None:
return CapabilityHealthResult.unhealthy("Client is not initialized")
if await self._client.ping():
return CapabilityHealthResult.healthy(message="Service ready")
return CapabilityHealthResult.degraded("Service did not respond")

Complete Provider pattern

python
from vega_capability_sdk import (
BaseCapabilityProvider,
CapabilityExecutionResult,
CapabilityHealthResult,
DependencyUnavailableError,
InvalidInputError,
PhysicalTerminationStatus,
)
class EchoProvider(BaseCapabilityProvider):
def __init__(self, adapter) -> None:
super().__init__()
self._adapter = adapter
async def on_initialize(self, context) -> None:
await self._adapter.connect(context.configuration)
async def on_execute(self, request, context):
context.validate_request(request)
text = request.inputs.get("text")
if not isinstance(text, str) or not text.strip():
raise InvalidInputError("text must be a non-empty string")
context.cancellation.raise_if_cancelled()
context.logger.info("echo started", action_id=request.action_id)
await context.progress.report({"phase": "working"})
try:
value = await self._adapter.echo(text)
except OSError as exc:
raise DependencyUnavailableError("Echo backend unavailable", cause=exc) from exc
context.cancellation.raise_if_cancelled()
return CapabilityExecutionResult(output={"echo": value})
async def on_health_check(self):
return (
CapabilityHealthResult.healthy()
if await self._adapter.health()
else CapabilityHealthResult.degraded("Echo backend unavailable")
)
async def on_shutdown(self) -> None:
await self._adapter.close()

The example intentionally keeps the adapter outside SDK types. Vendor clients, ROS nodes, device protocols, and AI services belong behind Capability-owned interfaces.

Test the composition

Test Provider hooks through public lifecycle methods so the test includes base state enforcement and error conversion:

python
from vega_capability_sdk import CapabilityExecutionRequest
from vega_capability_sdk.testing import ProviderContractHarness
request = CapabilityExecutionRequest(
execution_id="test-execution",
capability_id="example.echo",
provider_id="example.echo.python",
action_id="example.echo.run",
action_version="1.0.0",
inputs={"text": "hello"},
)
report = await ProviderContractHarness().run(provider, request)
report.raise_for_failures()

Pass the manifest ActionDefinition to the harness when output-schema validation should be part of the test.

Best practices

  • Override on_* hooks and preserve the base public lifecycle.
  • Establish stable dependencies during initialization, not module import.
  • Keep one execution's mutable state isolated from other executions.
  • Observe cancellation before and after external I/O.
  • Use context services instead of hidden Runtime imports or global state.
  • Return only facts the backend can support.
  • Keep errors, Progress, diagnostics, and logs bounded and free of secrets.

Common pitfalls

  • Returning a dictionary instead of CapabilityExecutionResult.
  • Mutating request inputs, configuration, metadata, or other frozen mappings.
  • Assuming deadline automatically interrupts Provider code.
  • Treating optional correlation IDs as SDK Task or Session objects.
  • Catching CapabilityError and replacing it with a generic exception.
  • Reporting confirmed physical termination without backend acknowledgement.
  • Accessing context.artifacts without checking for None.
  • Starting unmanaged background tasks that survive shutdown.