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
- discover
- validate
- start
- handshake
- configure
- execute
- stream events
- finish
- 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:
- Built into the Go host.
- A Go binary launched by Hovel.
- A Python process launched by Hovel.
- A Rust binary launched by Hovel.
- A pre-existing process Hovel connects to.
- 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.
- discover
- validate
- start
- handshake
- configure
- health_check
- ready
- serve
- reload
- stop
- cleanup
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:
- Use UTF-8 JSON-RPC 2.0 messages.
- Frame each message with an LSP-style
Content-Length: <bytes>\r\n\r\nheader. - Reserve stdout exclusively for framed protocol messages.
- Forward SDK logs as
module/lognotifications; do not print progress to stdout. - Capture stderr as process diagnostics for startup, schema, execution, and shutdown failures.
- Treat malformed frames, unexpected stdout bytes, and protocol timeouts as module failures.
- Host-to-module notifications such as
cancelare 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.