Architecture
Hovel uses a clean application core with adapters at the edges.
The domain package must not import CLI, TUI, REST, MCP, storage, RPC, concrete module code, or concrete service code.
Engine
hoveld is the local engine role and is not optional. It is served by the hovel mono-binary rather than a separate production binary. User-facing commands may bootstrap it, but execution happens through the daemon role.
It owns:
- Workspace database.
- Throw state.
- Chain execution.
- Module process lifecycle.
- Provider-backed payload and step behavior through module RPC.
- Service descriptor contracts; general service lifecycle management remains future work.
- Installed payload inventory.
- Artifact store.
- Event bus.
- Logging pipeline.
- Daemon-backed client attachment state for CLI, one-shot execution, and MCP.
- Future TUI and REST/OpenAPI attachment paths over the same application-service boundary.
The primary executable is one hovel binary with these product roles:
hovel cli ...
hovel throw <chain-file>
hovel daemon serve ...
hovel daemon status
hovel tui ... # not implemented yet
hovel tui is part of the product contract but may remain an explicit "not implemented yet" role until the TUI milestone. The production operator surface should center on hovel cli for stateful work and on a narrow one-shot shell surface for throwing a saved chain file.
cli and, once implemented, tui must auto-start or attach to the daemon role in local mode. The root CLI also accepts compatibility and developer entrypoints such as shell, command, run, direct registry roots, init, and status, but they are not the durable product contract. Shell one-shot execution is limited to saved chain files and daemon-management commands.
managed local mode: hovel cli starts or uses a local daemon
future TUI mode: hovel tui will start or use a local daemon when implemented
one-shot mode: hovel throw <chain-file> prompts for confirmation unless --now is passed
server mode: hovel daemon serve is explicitly launched and clients attach
Local managed-daemon mode is the implemented default for operator workflows.
Daemon Contract
Daemon-backed hovel operations must resolve a workspace, locate the matching local daemon, wait until it is healthy, then execute through application services. cli and, once implemented, tui may start a managed daemon when none exists. One-shot chain-file execution may start or attach to a local daemon, but it must use the same planning, confirmation, event, artifact, and persistence path as interactive execution.
Daemon rules:
- One active
hoveldper workspace. - A workspace lock prevents duplicate daemons from owning the same database.
- The daemon RPC contract is the stable v1 front-end contract. It uses HTTP POST with JSON request and response bodies over the local transport, is documented in Daemon RPC Contract, and has a machine-readable OpenAPI artifact at
reference/daemon-rpc.openapi.json. - The local client transport must support
cli, one-shot chain-file execution,tui, REST, and MCP attaching to the same daemon. - Unix-like systems should prefer a Unix domain socket under a workspace or runtime directory with filesystem permissions.
- Explicit daemon serving may also bind TCP with an address such as
tcp://127.0.0.1:9090for integration and remote-adapter development. - Windows should use a named pipe when Windows support is implemented.
- Daemon identity, socket path or listen endpoint, PID, start time, and health status should be inspectable with
hovel daemon statusand from the shared registry ascontrol daemon status. - Tests may use an in-process engine harness, but production commands should exercise the daemon boundary.
- Contract tests should keep the OpenAPI paths and human docs aligned with the registered daemon methods, while behavior tests prove throw, launch-key, session, log, and payload flows through the daemon boundary.
- Mock exploit modules must not perform network exploitation. They exist in-tree for tests and examples, should exercise the same module-loading path as ordinary modules where practical, and are not part of the final shipped module catalog.
Managed daemon ownership rules:
- If
hovel cli, or futurehovel tui, finds an already-running daemon for the workspace, it attaches and leaves that daemon running on exit. - If
hovel cli, or futurehovel tui, starts the daemon because none is running, that daemon is owned by the interactive session. - An owned daemon should shut down when the owning
cli, or futuretui, session exits. - Ownership must be explicit in tests: started-by-session daemons are stopped on exit; preexisting daemons are not.
- One-shot chain-file execution must not create hidden durable operator session state beyond the throw records, events, artifacts, and logs it explicitly produces.
Application Services
Application services are the only things front ends should call.
Target service boundaries. The current concrete source includes workspace, daemon, run/module execution, command registry, chain runtime, artifacts, events, sessions, and installed-payload inventory paths; service lifecycle and listener management remain target boundaries until the service runtime lands:
ModuleService
ManagedServiceService
ProviderService
TargetService
PlanningService
ThrowService
ArtifactService
EventService
WorkspaceService
ListenerService
SessionService
PayloadService
GuardrailService
Representative target methods, not all implemented today:
ListModules(ctx) ([]ModuleDescriptor, error)
InspectModule(ctx, moduleID) (ModuleDescriptor, error)
ListServices(ctx) ([]ServiceDescriptor, error)
StartService(ctx, ServiceStartRequest) (ServiceID, error)
StopService(ctx, ServiceID) error
PlanThrow(ctx, ThrowRequest) (ThrowPlan, error)
ConfirmThrow(ctx, ThrowPlanID, Confirmation) (ConfirmedThrow, error)
StartThrow(ctx, ConfirmedThrow) (ThrowID, error)
StreamEvents(ctx, ThrowID) (<-chan Event, error)
CancelThrow(ctx, ThrowID) error
ResolvePayload(ctx, PayloadQuery) (PayloadRef, error)
ResolvePayloadBytes(ctx, PayloadQuery) (PayloadBytes, error)
ListAvailablePayloads(ctx, PayloadQuery) ([]PayloadInfo, error)
ListInstalledPayloads(ctx, InstalledPayloadFilter) ([]InstalledPayload, error)
InspectInstalledPayload(ctx, PayloadHandle) (InstalledPayload, error)
ConnectInstalledPayload(ctx, PayloadHandle) (SessionRef, error)
CleanupInstalledPayload(ctx, PayloadHandle) (CleanupResult, error)
MarkPayloadRemoved(ctx, PayloadHandle, Reason) error
StartListener(ctx, ListenerRequest) (ListenerRef, error)
ListSessions(ctx, ThrowID) ([]SessionRef, error)
CheckGuardrails(ctx, ActionRequest) (GuardrailResult, error)
PayloadService is the application-service owner for payload discovery,
stamped artifact lookup, installed payload inventory, reconnect, refresh, cleanup,
and manual removal bookkeeping. Payload providers supply metadata and
provider-owned reconnect descriptors, but Hovel core validates and persists
installed payload records through this service.
StartThrow must not accept a raw ThrowRequest. A caller first asks for a plan, reviews the exact configuration that will be executed, records the explicit confirmation, and then starts the confirmed throw. This keeps CLI, TUI, REST, MCP, and one-shot chain-file execution on the same safety path.
Interactive throw behavior:
throwbuilds or refreshes the throw plan for the active chain.- If a matching confirmation already exists for the exact plan hash, Hovel starts the throw.
- If no matching confirmation exists,
throwdisplays the same review surface asreview, asks the operator to typeyes, records the confirmation, and then starts the throw. throw --nowbypasses the prompt but must still persist a confirmation record showing that the bypass was requested.confirmrecords a pre-confirmation without starting execution.reviewalways displays the current configuration review, requires the operator to typeyes, and records or refreshes the confirmation without starting execution.
Confirmations do not expire by time. A confirmation is invalidated by any change that changes the plan hash. The confirmation record should stay small: plan hash, timestamp, and client ID are required. Additional fields may be recorded when already available, but should not require heavyweight identity or account systems.
When launch-key policy is enabled, confirmation is entity-scoped. A pending throw snapshots the required live operation attachments and can start only after every required entity approves the same plan hash and policy flags. The agent-facing design is specified in MCP and Agent Operation.
Command Registry
Shared application command definitions live in a central registry. The registry owns command paths, aliases, options, help, validation, output modes, and handler bindings. The interactive CLI may add prompt-only affordances such as active-chain aliases and chain config interactive, but those affordances must delegate to the same session and application-service behavior.
The implemented operator registry exposes execution through chain and target setup, not a durable top-level run product command:
control daemon status
control init
op create <operation>
op use <operation>
op list
op inspect
chain create <chain>
chain use <chain-or-module>
chain add <module>
chain rename <chain> <name>
chain save <file>
chain load <file>
chain list
chain inspect
chain delete <chain>
chain logs
chain config set <key> <value>
chain config unset <key>
chain config list
module available
module installed
module inspect <module>
module search <query>
target add <target>
target clear
target bind <target>
target unbind <target>
target config set <target> <key> <value>
target config unset <target> <key>
target config list <target>
target set create <name>
target set add <name> <target>
target set remove <name> <target>
target set list
target set inspect <name>
target group create <name>
target group add <name> <target>
target group remove <name> <target>
target group list
target group inspect <name>
confirm
review
throw
throw list
throw inspect <throw>
payloads available
payloads installed
payloads inspect <payload>
payloads connect <payload>
payloads cleanup <payload>
payloads mark-removed <payload>
payloads refresh <payload>
artifact list
artifact inspect <artifact>
session list
session connect <session>
session tail <session>
session read <session>
session send <session> <data>
session close <session>
Operations and chains are durable operator resources, not just transient prompt variables. The selected operation owns chains, target inventory, target configuration, and target sets/groups; each chain owns its default target bindings by operation target ID; each client attachment owns its active chain selection. Target commands mutate the caller's active operation and, in chain context, the active chain binding. Saved chain files are the portable representation for shell one-shot execution.
The one-shot shell form is:
hovel throw <chain-file>
hovel throw <chain-file> --now
The non---now form must display the same configuration review as interactive throw and require the operator to type yes.
Adapters consume the registry differently:
climode renders registry definitions as go-prompt completions, contextual help, and validated interactive invocations.tuimode may use the registry for command palettes and action metadata.- The one-shot chain-file command renders only the options needed to load, review, confirm, and execute a chain file.
- MCP and REST may use the registry metadata where useful, but they must still call application services through typed APIs.
Command handlers receive validated invocation objects from the registry layer. They must not parse raw shell strings or argv themselves.
Package Layout
The repository is split into sparse-checkout friendly slices. The framework binary and daemon live in a standalone core workspace; SDKs, modules, and docs live beside it:
hovel/
core/
cmd/
hovel/
internal/
app/
domain/
adapters/
infra/
moduleruntime/
testmodules/
schemas/
tools/
lint/
release/
tests/
sdk/
go/
python/
rust/
modules/
examples/
go/
python/
rust/
squatter/
provider/
client/
windows/
tools/
lab/
docs/
site/
spec/
assets/
demo/
tools/
repo-tools/
Multi-Client Attach
Multiple clients should be able to observe the same engine state:
- One operator creates or activates an operation.
- One client selects chain
alpha; another client selects chainbetain the same operation. - Future MCP tooling can attach to the same operation and choose either chain.
- One-shot invocations can import and throw a saved chain file without mutating another client's active chain selection.
- CLI prompt commands query and control the same daemon-owned operation and chain state.
- The daemon publishes operation-scoped chain topics such as
operation/<operation>/chain/<chain>/logs. - Clients attached to the same operation and chain see the same chain logs.
- Clients attached to other chains do not receive those logs by default.
- Service logs and throw events remain centralized and can be correlated back to the owning operation and chain.
Workspace Database
Every workspace owns metadata at <workspace>/workspace.json and a SQLite database at <workspace>/workspace.db. The database is the durable state store for operator session state, operations, chains, targets, steps, config, throw plans, confirmation records, throw records, installed payload records, installed payload state-change history, events, artifact metadata, and final throw state. Current workspace initialization creates file directories for artifacts, logs, modules, throws, and services; provider-specific payload, listener, or session directories can be added when a provider needs file-shaped state.
Installed payload records are global workspace inventory with chain, target, throw, run, and artifact provenance. They are not owned by the active chain selection. Listing installed payloads is a local database read by default; any target probing or provider reconnect attempt must be an explicit refresh, connect, or cleanup action.
Schema changes must go through the formal migration system. Each migration has a contiguous integer version, a stable name, and a checksum recorded in schema_migrations. Startup and workspace initialization must apply pending migrations idempotently and reject unknown, renamed, or modified applied migrations.
Test Harness
Application services must be constructible in-process for unit, contract, and integration tests. The in-process harness should use the same domain logic, guardrail checks, event bus contracts, descriptor validation, and storage interfaces as hoveld.
Production hovel cli, one-shot chain-file execution, and future hovel tui behavior should still cross the daemon boundary for daemon-backed operations. Tests may use the in-process harness to keep core behavior fast and deterministic, but daemon-contract tests must verify workspace locking, daemon discovery, managed daemon ownership, health checks, client attachment, and execution through the local transport.