HOVEL // chapter 06
Chapter 06 Part 02 / Runtime Platform

Modules and Services

Modules and services are separate domain concepts with separate lifecycle expectations.

Module Runtime

The implemented module runtime is jsonrpc-stdio. Every module is a separate process that speaks Content-Length framed JSON-RPC 2.0 on stdin/stdout. Module packages provide one or more host-platform launch entries. A launch entry may run a native binary, a Python module through Hovel-managed python-build-standalone, an operator-supplied interpreter, or a bundled interpreter.

This is the runtime boundary for all supported languages today. A module may live in this repository and be published as a release package, but it still goes through the same catalog loading, handshake, schema, execution, logging, session, artifact, and installed-payload contracts as third-party modules. Production modules should not rely on a private native built-in path.

The in-tree examples are deliberately benign and exercise the orchestration surface: survey, exploit, and session modules exist in Python, Go, and Rust. These examples and Squatter now live in the modules/ slice. Use task modules:examples:ci for the examples, task modules:ci for the complete slice, and task release:modules-package for installable archives and indexes. These are separate from the core-only gate but compose into full-checkout CI. Python examples can be linked during development or packaged with a managed Python launcher.

Module Lifecycle

Module Lifecycle
  1. discover
  2. validate
  3. start
  4. handshake
  5. configure
  6. execute
  7. stream events
  8. finish
  9. shutdown

Every module must provide:

handshake() -> ModuleInfo
schema() -> ModuleSchema
execute(request: ExecuteRequest) -> ModuleResult
shutdown() -> ShutdownResult

Session-capable SDKs also implement session/write, session/read, and session/close. During catalog inspection, Hovel calls handshake, schema, optional step.describe, and optional mesh.describe. SDKs that do not expose step or Mesh contracts either return an empty step set, an empty Mesh descriptor, or a missing-provider JSON-RPC error; the runner treats those as ordinary modules without that extension.

Step-provider modules share the base lifecycle and add generic step methods over the same JSON-RPC stdio channel. Hovel uses those methods to learn live step contracts, prepare pure local values, execute confirmed steps, and clean up durable handles. Go and Python implement this step surface today; Rust does not yet dispatch step.* methods.

step.describe() -> StepContractSet
step.prepare(request) -> StepPrepareResult
step.execute(request) -> StepExecuteResult
step.cleanup(request) -> StepCleanupResult

Payload providers may also expose provider-specific RPC methods. The Go SDK is the complete provider surface today:

list_payloads(query) -> []PayloadInfo
resolve_payload(query) -> PayloadInfo
prepare_listener(request) -> ListenerRef
generate_payload(request) -> PayloadArtifactSet
connect_session(request) -> SessionRef
cleanup_payload(request) -> CleanupResult
read_payload_chunk(request) -> PayloadChunk

Mesh providers follow the same module runtime path rather than a separate plugin system. A module can add mesh.describe, mesh.topology, mesh.beacons, mesh.task, and mesh.open_stream to expose one-node or routed node operations through the existing package, catalog, logging, session, and daemon RPC contracts.

Install steps and ordinary execute paths may return explicit installed-payload descriptors. Hovel core validates those descriptors and persists workspace inventory; the provider owns only payload-specific reconnect and cleanup metadata. The first core design is Squatter Payload Provider.

Service Runtime

Services are long-lived capabilities in the domain model. Service descriptors and validation exist today; a general external service process runtime remains design work. Current provider behavior that operators can exercise is exposed through module RPC, especially Go payload-provider modules.

A future service process may be:

  1. Built into the Go host.
  2. A Go binary launched by Hovel.
  3. A Python process launched by Hovel.
  4. A Rust binary launched by Hovel.
  5. A pre-existing process Hovel connects to.
  6. A future remote service.

Future service processes should expose typed operations to the Hovel engine.

Service Lifecycle

The lifecycle below is the target model for a future external service runtime. The current repository validates service descriptors but does not discover, launch, health-check, or stop general service processes.

Service Lifecycle
  1. discover
  2. validate
  3. start
  4. handshake
  5. configure
  6. health_check
  7. ready
  8. serve
  9. reload
  10. stop
  11. cleanup

Service states:

Service States
  • registered
  • starting
  • configuring
  • healthy
  • degraded
  • failed
  • stopping
  • stopped

Start modes:

manual       operator starts explicitly
on_demand    started when a provider or service is requested
run_scoped   started for one run and stopped after cleanup
workspace    started with workspace or daemon
external     Hovel connects to an already running service

A future service process runtime should implement or emulate:

handshake() -> ServiceInfo
schema() -> ServiceSchema
start(request: StartRequest) -> StartResult
health() -> HealthResult
stop(request: StopRequest) -> StopResult

Service process management is not the same implemented path as module execution. Provider behavior that is available in the current tree is exposed through module RPC, especially the Go PayloadProvider interface and the in-tree Squatter provider command.

Future provider services may additionally implement typed operations for their resource kind.

Listener service methods:

start_listener(request) -> ListenerRef
stop_listener(listenerRef) -> StopResult
list_sessions(listenerRef) -> []SessionRef
attach_session(sessionRef) -> SessionStream
close_session(sessionRef) -> CloseResult

Protocol Position

The implemented module protocol optimizes for simplicity and observability. JSON-RPC over stdio is already shared by Python, Go, Rust, and the Squatter provider; future socket or service protocols should not change the module authoring contract without new contract tests and updated SDK guides.

Stdio framing rules:

  1. Use UTF-8 JSON-RPC 2.0 messages.
  2. Frame each message with an LSP-style Content-Length: <bytes>\r\n\r\n header.
  3. Reserve stdout exclusively for framed protocol messages.
  4. Forward SDK logs as module/log notifications; do not print progress to stdout.
  5. Capture stderr as process diagnostics for startup, schema, execution, and shutdown failures.
  6. Treat malformed frames, unexpected stdout bytes, and protocol timeouts as module failures.
  7. Host-to-module notifications such as cancel are best-effort; process teardown remains host-owned.

Do not use the same byte stream for both RPC frames and arbitrary module output. Modules that write arbitrary bytes directly to file descriptor 1 are incompatible with stdio RPC and should use a later process mode or a dedicated side-channel transport.

Language SDKs

Module authors use the language guide for their implementation language. All three SDKs expose the same base lifecycle under language-appropriate names.

Language Authoring shape Launch shape Examples
Python Subclass HovelModule; implement sync or async run and optional step or Mesh hooks. Package launcher using managed Python, an interpreter path, or a bundled interpreter. modules/examples/python/mock_survey, mock_exploit, mock_exploit_session
Go Implement hovel.Module; optionally StepProvider, PayloadProvider, or Mesh operation interfaces. Package launch entry pointing at a compiled binary. modules/examples/go/mock_survey, mock_exploit, mock_exploit_session
Rust Implement the hovel::Module trait, optional Mesh methods, and return Outcome. Package launch entry pointing at a compiled binary. modules/examples/rust/mock_survey, mock_exploit, mock_exploit_session

Minimal Python shape:

from hovel_sdk import HovelModule, Context, Result

class HelloModule(HovelModule):
    name = "hello"
    version = "0.1.0"
    module_type = "survey"

    def run(self, ctx: Context) -> Result:
        ctx.log.info("hello from module", extra={"target": ctx.target})
        return Result.ok({"target": ctx.target})

The Python SDK does not expose decorator-based module or service APIs. Use HovelModule, class attributes, and serve(MyModule()). Go and Rust equivalents are documented in their language pages.

Registration

Runtime registration comes from installed or linked module packages. The package manifest supplies launch metadata; the module process supplies authoritative runtime schema through RPC.

hovel module install ./hello-0.1.0.tgz
hovel module install --link /home/me/dev/hello-module
hovel module installed
hovel module inspect hello@0.1.0

Logging

The Python SDK installs a logging handler when serve starts. It forwards standard Python log records to the host as module/log notifications.

from hovel_sdk import setup_logging

setup_logging()

Calling setup_logging manually is only needed for custom harnesses. In normal modules, prefer ctx.log or standard logging after serve is running. Go modules use ctx.Log; Rust modules use ctx.info and ctx.error.