Reaktor Workbench Layers — Tooling, Kernel and Hosts
The workbench has one workspace backend that can run without a window. The desktop, terminal CLI and MCP host compose that backend for different operators. External execution belongs in reusable tooling; workspace decisions belong in the kernel; presentation belongs in the host.
This is the implemented architecture as of 13 September 2026. The authority section separates the deployed auth backend from unfinished operator qualification and execution admission. Consolidation of the two command lines onto reaktor-cli remains planned. The Control Plane, Graph Protocol and Graph Kernel Analysis describe the larger direction. Distributed sessions, authenticated remote control and general graph mutations are not implied by the local implementation described here.
The layer map
Desktop window Terminal Agent client
Compose / engine reaktor-workbench MCP
│ │ │
└──────────────────────────────┼───────────────────────┘
▼
BestBuds workspace kernel
context · policy · plans · receipts · sessions
│
KernelGraph
operations → database / auth / connections
│ │
▼ ▼
reaktor-graph-runtime reaktor-tooling
typed wiring/lifecycle discovery/execution/adapters
│
▼
databases · cloud · devices · IDEs
MCP protocol: reaktor-mcp Loopback HTTP transport: reaktor-tooling
UI graph facade: reaktor-graph Graph editor/layout: reaktor-flow
The arrows describe dependency and ownership. They do not mean the CLI talks to the running desktop. Each process creates its own kernel unless a host explicitly shares an instance inside that process.
| Layer | Owns | Does not own |
|---|---|---|
reaktor-tooling — open source | Workspace discovery, task/plan/receipt contracts, supervised process execution, cancellation, bounded output, device/database/IDE adapters and loopback MCP transport | Product recipes, environment selection, approvals on behalf of an operator, or UI state |
reaktor-graph-runtime — open source | The existing graph/node implementation, typed ports, wiring, actors, lifecycle, dependency scopes, navigation and graph serialization | Compose, graph rendering or BestBuds policy |
reaktor-mcp — open source | Transport-independent JSON-RPC and read-tool/resource contracts | Workspace authority, product tool selection or a desktop window |
modules/kernel — closed workspace backend | Workspace catalog, environment context, planning and approval rules, run receipts, persistence, database/auth sessions and MCP read policy | Pane composition, native windows or graph layout |
modules/engine and targets/reaktorDesktop | Workbench panes, navigation, editors, selection, rendering and registration of the graph mounted by the window | A second backend execution path |
targets/reaktorCli | Arguments, terminal output, exit codes and process lifetime | A second task catalog or policy engine |
reaktor-cli — open source | The generic framework CLI | The closed BestBuds workspace backend; it is a different executable from reaktor-workbench. Consolidation onto one command line is decided — see One command line |
The graph runtime and kernel currently require Java 25. The standalone JVM tooling adapters target Java 21. The dependency checks reject Compose, Skiko and frontend modules in the headless runtime/kernel.
What “kernel” means here
ReaktorKernel is the workspace backend. Its KernelGraph connects services through typed provider/consumer ports:
- Workspace operations: the shared
OperationalControlPlane, which holds context, catalog and receipts. - Data documents: independent database sessions, bounded schema/query operations and private pinned results through the reviewed execution path.
- Auth: directory inspection plus an explicit operator authority handle; directory visibility does not grant execution authority.
- External connection checks: reviewed reads that produce connection evidence through ordinary operation receipts.
This in-memory service graph is distinct from the remote Memgraph database. It also does not claim to implement every proposed immutable graph-IR or distributed-protocol feature. An edge proves local wiring; a successful, dated remote read is separate evidence that an external connection worked.
Graph composition does not replace conventional APIs. Callers can use kernel.operations, database sessions, connection checks and plain reaktor-tooling contracts directly. Graph ports make ownership and dependencies inspectable. Reusable tooling remains usable without adopting a graph.
Lifetime and host boundaries
Opening a kernel creates the operations service, graph-owned sessions and dependency scope. Closing it stops its MCP listener, closes graph sessions and both native and process executors, and cancels outstanding work. Additional database/auth sessions created through the kernel are registered in the graph and may be closed earlier by their consumers.
The desktop bootstrap owns its kernel. DesktopWorkbenchGraph mounts that same KernelGraph in a container and borrows its database/auth sessions. Closing or remounting a pane must not close the backend. Closing the window disposes the bootstrap-owned kernel. A standalone desktop graph can own a fallback kernel when there is no bootstrap owner.
The CLI and headless MCP host have separate default state files from the GUI. They share implementation and policy, not live memory. An explicit state file is a local persistence choice, not a synchronization protocol; concurrent hosts must not write the same file.
reaktor-graph re-exports the runtime and adds Compose integration. Existing UI imports continue to work. Headless code depends on reaktor-graph-runtime directly, so importing the graph does not pull a renderer into the kernel.
One operation path
The kernel decides what a task means in the selected workspace and environment. Tooling carries out the prepared execution:
discover → inspect/preview → seal definitions and plan
→ check environment, effect and required authority
→ approve when required → supervised execution
→ reconcile output and artifacts → redacted receipt
previewPlan(taskId) does not run the task. A required approval binds to the exact plan fingerprint; changed source definitions, environment, native operation or arguments require a fresh plan. Native operations carry typed provider requests. The process adapter uses argument vectors. Both have bounded capture, cancellation and timeouts. The kernel retains the product policy and records the result.
The desktop and CLI enter this same path. MCP currently exposes reads only: it cannot execute a task, approve a plan or mutate a graph. The broader observe/propose/approve/execute agent workflow remains a design direction, not a hidden alternate API.
MCP is an adapter over the kernel
reaktor-mcp defines the protocol and tool contracts. reaktor-tooling provides the loopback HTTP server/client, request bounds and Host/Origin checks. KernelMcp chooses the product reads and redacts their results. Desktop MCP adds the app registry and mounted graph/architecture reads.
| Read | Meaning |
|---|---|
control_plane_snapshot | Redacted workspace catalog, resources and execution receipts |
operation_plan | The task's plan requirements; no execution or approval |
kernel_graph | The actual graph owned by this kernel instance |
connection_status | Recorded connection evidence for the selected environment; does not contact providers |
Desktop graph_snapshot | The selected app graph, with provenance identifying an active window, headless registration or factory fallback |
Desktop architecture_snapshot | The architecture projection of that resolved graph plus the operational overlay |
The loopback endpoint is local and unauthenticated. It is not an authenticated remote control service. A kernel_graph result is not proof that a GUI is mounted, and a factory graph is not reported as an active UI session.
External connections and credentials
Connection checks cover native JVM database adapters, remote D1 reads, Worker health, and authenticated Worker bindings for PostgreSQL/Hyperdrive, Memgraph, ClickHouse, D1, Durable Objects, service bindings, R2 and KV. The provider remains the system of record. A graph edge or configured binding is not a health check.
The kernel records the task, environment, run identity, time and result. A malformed body or application error does not count as healthy merely because HTTP or the process succeeded. Sensitive output is not retained across restarts; an old successful receipt is labeled RecordedSuccess until a new check supplies current evidence. A bounded read proves that path and operation at that time, not every product workflow or write permission.
Credentials are materialized at the execution/session boundary and excluded from graph serialization, MCP output and persisted workspace models. PostgreSQL inspection is scoped to an explicitly selected environment. Native database credentials and a Worker's Hyperdrive credential are different access paths; success on one does not attest the other. Database Studio additionally enforces its inspector-role policy.
Infrastructure execution on the JVM
Implemented 12 September 2026: the workbench's Kubernetes inventory/events/logs, PostgreSQL inspection and credential loading, Memgraph inspection, and ClickHouse inspection execute through JVM libraries. Desktop and CLI use the same kernel path. These operations have empty process argv and cannot fall back to a shell.
flowchart TD
Hosts[Desktop and CLI] --> Kernel[Kernel: context, plans and receipts]
Kernel --> Native[Tooling: native JVM executor]
Native --> Kube[Official Kubernetes Java client]
Native --> PG[PostgreSQL JDBC]
Native --> Bolt[Memgraph Bolt driver]
Native --> CH[ClickHouse HTTP client]
Kube --> Tunnel[Session-owned Kubernetes API port forwards]
Tunnel --> Private[Private Memgraph and ClickHouse services]
Native --> Worker[Authenticated Worker operation client]
Worker --> Edge[Worker bindings: D1, DO, KV, R2, Hyperdrive, VPC, services]
| Provider | Execution and transport | Implemented workbench operations |
|---|---|---|
| Kubernetes | Official io.kubernetes:client-java:27.0.0; existing certificate/token kubeconfig | Namespace inventory, object events, bounded pod logs, exact named credential read and API port forwards |
| PostgreSQL | PostgreSQL JDBC over TLS; host maps its credentials into standard PG* fields | Connection/catalog health; sealed SQL, typed results, JSON plans and statistics; explicit operator or strict-inspector profile |
| Memgraph | Neo4j Java Bolt driver over a JVM-owned Kubernetes service tunnel | Six registered catalog/graph inspections, bounded paging, graph results, EXPLAIN and query timing |
| ClickHouse | JDK HTTP client over a JVM-owned Kubernetes service tunnel | Sealed SQL with server read-only settings; JSONCompact retains duplicate column names, query IDs, statistics and EXPLAIN |
| Worker | JDK HTTPS client with short-lived service token | Capability discovery, eight binding checks and six concrete D1/R2/KV/Durable Object store inspectors |
Google Pub/Sub uses the JVM with Google's authentication library and bounded calls to the Pub/Sub and Cloud Monitoring APIs. Its topic/subscription pages retain server continuation tokens. Reading application delivery observations uses a separate Reaktor service token; Google credentials are never sent to an application receiver.
The generic contracts and clients live in reaktor-tooling/infra; they can be used as ordinary library APIs without a graph. KernelOperationExecutor chooses native or process execution from the catalog. Environment mapping, allowed targets, classification and private session admission remain in the product kernel. Native requests bind the typed operation, selected credentials, definition digests and limits into their plan fingerprint. Serialized plans and MCP projections omit credentials and private execution bindings.
A native execution owns a resource session: clients, statements, responses and tunnels close with completion, cancellation, timeout or kernel shutdown. Queries and results use owner-only files outside the workspace. Query text never becomes command-line arguments or persisted run output. The extra lookahead row is retained internally so the UI can report truncation correctly.
This migration covers the workbench's inspection paths. Existing deployment, database import/dump, CLI tunnel and maintenance scripts still exist as explicit process recipes. D1 management recipes still use Wrangler; interactive SQL and catalog inspection use the authenticated owning Worker. Application Workers continue using their runtime bindings, including Hyperdrive and VPC; this is a separate path from a desktop database connection. A new native administration operation must be introduced as a typed contract and pass the kernel's authority/approval rules before replacing its process recipe.
Kubernetes configurations using executable credential plugins are currently rejected by this native path. The configured cluster uses certificate credentials. No sidecar or new always-running local proxy was added: the kernel owns short-lived API tunnels. The existing server-side Memgraph gateway remains the Worker's bridge to Bolt.
Service tunnels resolve the service selector to a ready pod, resolve numeric or named target ports, and bind an ephemeral listener on 127.0.0.1. The official client's WebSocket stream primitives carry the traffic. The kernel owns the stream before the handshake starts, so cancellation also closes a stalled handshake. API clients are scoped to the operation; no global Kubernetes client is installed. Tests exercise traffic in both directions, cancellation, service selection, secret decoding, pagination refusal and log limits.
Worker as an execution host
The data Worker now exposes the shared reaktor.worker.operations.v1 contract:
GET /_reaktor/operationsreturns worker identity, environment and operation descriptors, including provider, effect and deadline.GET /_reaktor/operations/<id>?requestId=<id>executes one advertised read and returns an operation receipt with matching request, worker, environment, status and duration.postgres.inspect,memgraph.inspect,clickhouse.inspect,d1.inspect,durable-object.inspect,worker-service.inspect,r2.inspectandkv.inspectexercise the Worker's actual bindings.
Routes use the Worker's existing service-request authentication. The JVM client mints a short-lived service token, discovers the catalog, admits only bounded reads and validates the response identity before recording success. Redirects, oversized responses, unadvertised operations and mismatched environments/receipts fail. Provider failures and timeouts produce structured failure responses without provider secrets.
The Worker owns edge-local access: D1, Durable Object instances, KV, R2 and service bindings. A Durable Object's owning Worker performs its storage operation; the JVM cannot connect to that storage as a remote SQLite server. Memgraph uses the existing JVM gateway over VPC. ClickHouse uses VPC HTTP and PostgreSQL uses Hyperdrive. The new ClickHouse inspection performs a read; the separate legacy write probe is not used by this catalog.
This is the first implemented operation-host contract. Tenant-aware operator login, per-operation authorization, administrative writes, asynchronous job/stream protocols and shared cross-host audit storage remain planned. The deployed service token is not a tenant-superadmin session.
Authority and strict SQL — implementation and remaining work
The canonical Data inspection authority chapter owns tenant/operator grants, desktop sign-in, strict versus operator SQL and the remaining admission gates.
Data workspace projections
The canonical provider inspection chapter owns catalogs, queries, plans, pagination, result history/export and provider-specific evidence limits. All data-layer chapters are grouped in the Data Layer reading map.
Auth workspace projections
The Auth tab reads heimdall through the same kernel session the Data tab uses, and reads nothing
else: every surface below is a bounded, read-only projection. No screen in this tab writes, and the
credential-provisioning tasks that do write are ordinary reviewed operations with a confirmation
phrase and a second approval, not pane controls.
Screens read what they need when they are opened. A screen asks only for directories it does not already hold for the current scope, waits while the session is busy, and stops asking after a failure — so a failing query produces one failure and a Refresh control, not a retry loop.
| Surface | Auth tab behaviour | Boundary |
|---|---|---|
| Identity | Principal directory joined with credentials, grants, memberships and last-seen; search that reaches the database across email, ids, names and scopes; paging; population composition by kind, status and provider | One page at a time. A full page means more rows may exist, never a total; nothing counts the remainder |
| Tokens | Offline JWT decode with claims in plain language, epoch claims rendered as times, and the structural and temporal checks a verifier reaches, in its order. The kid is compared against signing_key | The signature is never verified — that needs the key. Opaque rkr_ and rkt_ tokens have no readable content by design and are reported as such rather than as a decode failure |
| Roles & Permissions | Roles, permissions and grants read together; a matrix of roles against resources derived from resource:action permission names; permissions carried by no role; permissions demanded by a secured port | Reaktor roles do not inherit, so no hierarchy is drawn. The resource axis is derived from the permission name because the schema has no resource column |
| Sessions | Sessions, refresh families, PATs, service accounts, memberships, principals and access issuance, each scopeable to one principal | The session record carries no device, address or user agent; those read as not recorded rather than as blank |
| Audit | Opens on failures rather than on the newest row; the trail filtered by principal, event type, reason and time; the event taxonomy read rather than assumed; attempts threaded by request_id | Retention is whatever the database has kept — the trail declares no policy of its own. A chain of one is reported as unthreaded, not as a complete attempt |
| Security | Source-observed posture over the reaktor-auth checkout and the selected graph scope, plus live findings: principals no longer active that still hold a usable refresh token, live tokens carrying wildcard or auth-administrative scope, and access that is granted and unexercised | Findings are joins over directories already read. An unused-access claim is only as good as the audit retention behind it, and the window is stated on the finding |
| Providers | Provider adapter inventory, runtime capability ports, signing-key states, and the per-app auth_provider_client rows | No live discovery document or JWKS fetch. Client rows are reported as configured and not consulted, because verification resolves the first provider matching the kind and takes its audience from global configuration |
| Simulator | The real LocalAuthorizer verdict for a component's declared secured port, with the kernel's gate order mirrored beside it and the requirement that failed named | The breakdown is a mirror of the kernel, not the kernel. When the two disagree the screen says so rather than presenting a reason it did not derive |
Identifiers are masked by policy — addresses keep their domain, ids keep a distinguishing prefix — with an explicit reveal. Exports carry the environment, search, page and mask state that produced them, and export what the screen shows.
Two findings here are the security review's own, computed rather than asserted: a disabled principal keeps minting access tokens until its refresh token expires because rotation does not re-check principal status, and mint authority constrains who may mint but not what is minted. The schema records no token creator, so the "minted for another principal" half of the second finding is absent rather than approximated.
Verification evidence
The Migrations view now separates source definitions from live history discovery. PostgreSQL recognizes Flyway, Liquibase, Atlas and Supabase ledger column formats; D1 recognizes its migration table. Custom schema/table names, explicit SELECT-denied results, unrecognized formats, 100-row pages and refresh are supported. Provider fields remain intact: a recorded migration is not proof of present schema equality or successful end-to-end rollout. Live discovery passed PostgreSQL and both D1 databases; the D1 ledgers returned two and one entries respectively. The recognized PostgreSQL ledger was unreadable to the active role, and two other table formats were left unclassified. SQLite-backed UI tests exercised discovery and both paging directions. Semantic diff, lint, rehearsal and reviewed apply remain planned. Provider references: D1 migrations, Flyway history, Liquibase history, Atlas revisions.
On 13 September local time, a packaged PostgreSQL regression was reproduced: the trimmed desktop JDK omitted java.management, which the JDBC driver uses. The distributable now includes that module, and a linkage failure ends the query with an actionable error instead of leaving it running. Verification uses the packaged jars and module list. Live PostgreSQL tests passed complete catalog reads, prepared parameters with exact decimals and NULL, parameterized EXPLAIN, invalid parameter counts and nine access/object inspections. All three updated owning Durable Object classes passed name/ID overview, SQLite catalog, SQL and EXPLAIN through the deployed broker and kernel; an isolated workerd fixture additionally verified truncation, large blobs and rejected writes. Compose interaction tests exercised instance SQL, message decoding/filter persistence and focused schema exploration.
On 12 September 2026, all 15 configured production connection checks succeeded, including all eight authenticated Worker binding operations. Native Kubernetes namespace inventory, PostgreSQL JDBC inspection using the JVM-loaded credential, Memgraph Bolt and ClickHouse HTTP were exercised against the configured services. All six registered Memgraph inspections passed through DatabaseStudioSession, including graph decoding and pagination.
A subsequent automatic Data connection sweep passed for all 12 distinct production data sources: PostgreSQL, ClickHouse, Memgraph, two D1 databases, two R2 buckets, KV, three Durable Object classes and the GCP Pub/Sub project. This sweep exercises the same startup path as Desktop, including loading the PostgreSQL credential through Kubernetes. The eight private store-query operations passed independently; ChatService returned object identity, connection count and alarm metadata, and PortalSession returned object identity, stored-record presence, storage bytes and alarm metadata. Failed probes retain a failed connection state; a successful ordinary query reconnects its admitted source. Live-test opt-ins force fresh execution and cannot reuse a cached Gradle success.
The expanded live kernel tests exercised PostgreSQL operator SQL, schema, statistics, JSON EXPLAIN and a private SQL error; ClickHouse typed results, statistics and schema; server-side filtered/sorted metadata pages in both SQL engines; both D1 databases including catalog PRAGMAs, size metadata and query plans; both R2 buckets; KV; all three Durable Object inspection paths; and Pub/Sub catalog, Monitoring and authenticated receiver reads. PostgreSQL strict-inspector rejection was rechecked separately and remains enforced. The packaged desktop previously executed Pub/Sub and R2 reads, displayed elapsed times, navigated an R2 folder and showed an existing text object's metadata and preview. Its MCP loopback test passed. A fresh twelve-source connection sweep passed at 18:40 UTC on 12 September, after the auth server deployment. On 13 September local time, the updated desktop opened visibly with the Data label and Sign in menu; the browser callback and subsequent rebuilt-app Keychain access blocked completion of the real operator login.
Local tests exercise independent documents and cancellation, two real SQLite query results pinned and compared by unique keys, duplicate column names, null/missing cells, bounded pin retention, PostgreSQL plan metrics, revoked/wrong-tenant authority, browser callback validation, timeout recovery and native Keychain persistence/rotation/isolation. The affected auth, tooling, graph, kernel and Data tests pass. The latest broader desktop run found failures in native focus while the Mac was locked, retired preview selectors, an editor input journey, graph pan precision and tests assuming Local by default. The Local/Production and current DevTools preview journeys were corrected and passed targeted reruns. Native focus, editor input and graph pan still require release qualification; the complete desktop suite is not green. These checks do not replace the outstanding authenticated packaged-desktop, administrative-write and full provider-feature acceptance journeys.
The rebuilt package passed all twelve production connection reads on 13 September. PostgreSQL additionally passed query, EXPLAIN, initial table browsing and statistics, with 58 complete catalog tables and 531 columns. The validation package is isolated from the existing installed app while its Keychain access is being resolved. The latest graph, prefix, instance and chart design boards are authored in Pencil; native saving and final on-screen acceptance remain pending an unlocked Mac.
The subsequent public-row regression gate passed all 34 public relations (26 tables and eight views) through the packaged jars, JDK module list and real kernel sessions. It required nonempty bounded samples from the 19 tables independently confirmed populated, alongside catalog, EXPLAIN and statistics checks. All twelve production connections passed in that run. Kernel and Compose tests cover default-denied rows despite SELECT grants, unknown evidence, view semantics, and refreshing the displayed policy observation. No application row contents were emitted by the live sample gate. This corrects the earlier validation gap: an initial browse returning zero was previously counted as success. The existing native window still had the older catalog UI; native input automation failed, so the updated validation package's interactive desktop journey is not claimed as passed.
At verification time, the selected GCP account returned three topics and zero subscriptions in the configured project; a separate gcloud read agreed. Successful catalog and receiver requests establish read connectivity, not message delivery. Backlog metrics and recent-message pagination need actual subscriptions and receiver observations to supply live evidence. The cursor regression test covers tied timestamps, identical redeliveries and newer arrivals without publishing or acknowledging messages in production.
These are dated read receipts. They do not certify administrative writes, all Durable Object instances, every tenant, or access from a future environment. Runtime evidence must be refreshed through the kernel; configuration alone does not establish health.
Using the implemented layer
From the BestBuds workspace, with Java 25 available:
./gradlew :reaktorCli:installDist
targets/reaktorCli/build/install/reaktor-workbench/bin/reaktor-workbench --workspace . tasks
targets/reaktorCli/build/install/reaktor-workbench/bin/reaktor-workbench --workspace . inspect kernel_graph
targets/reaktorCli/build/install/reaktor-workbench/bin/reaktor-workbench --workspace . --environment production --postgres-inspector connections --check
targets/reaktorCli/build/install/reaktor-workbench/bin/reaktor-workbench --workspace . mcp --port 8766
./gradlew :reaktorDesktop:run
The --postgres-inspector flag loads the selected environment's dedicated connection from the configured Kubernetes Secret directly in the JVM. Desktop loads this connection automatically for production; a desktop JAR can also explicitly select an inspector environment with -Dreaktor.postgres.inspector.environment=production before -jar. Restart the application to load new code. The actual credential role-policy label remains enforced.
The desktop's --mcp-only mode still loads its desktop graph adapter without opening a window. The CLI's mcp command is the headless kernel host with no Compose dependency. Global CLI options precede the command. connections --check exits nonzero if any selected check fails or is blocked.
Where a new capability belongs
Put a reusable external adapter and its execution contracts in reaktor-tooling. Put a product task recipe, environment mapping, authority rule or redaction rule in the workspace kernel. Register a graph node only when it contributes useful service wiring or lifetime ownership. Add CLI arguments, MCP read projection and desktop presentation as thin adapters over those contracts.
Verification follows those boundaries: tooling tests exercise process supervision and adapters; runtime/kernel checks guard dependencies and lifecycle; kernel tests cover policy, receipt sharing and response validation; desktop tests verify that its panes and MCP reads borrow the correct graph. Remote checks supply dated evidence separately from those local tests.
A common daemon, cross-process event synchronization, remote authenticated sessions, MCP mutations and the full graph protocol are still future work. They should extend the shared contracts and authority path rather than add a parallel backend.
One command line
Decided 12 September 2026: reaktor-cli is the Reaktor command line. targets/reaktorCli (reaktor-workbench) is transitional and folds into it. Everything in this section is design, not implemented architecture. The authority section above records its implementation separately.
Two command lines exist because the operational model lives in a closed module. reaktor-workbench is a kernel host and has plans, environments, approvals and receipts. reaktor-cli cannot depend on the kernel, so it grew a second model of a workspace: a target is a targets/ folder, a task is an npm script name, and effects are inferred from argument words at call time. That weaker model owns the command surface an operator actually types.
| Observed, 12 September 2026 | Consequence |
|---|---|
| The kernel catalog resolves 502 tasks in eight domains; 293 are approval-gated, 258 carry a confirmation phrase, 56 require a second approver | None of it is reachable from reaktor, whose model has nowhere to record an effect, an environment or an approval |
ReaktorEnv.run reaches the sealed tooling catalog only through toolingInvocationFor, which recognises npm run <script> and ./gradlew <task> | Everything else falls back to a third safety classifier kept inside the CLI |
projectTargets is derived from four folder-based sources | A declared target with no targets/<name>/ folder is invisible: reaktor new scaffolds a project that reaktor graph reports as targets (0), while reaktor explain web advertises reaktor web dev, which is not a registered command |
| Ergonomic shortcuts are registered as commands in one flat namespace with first-come-wins reservation | A target named test or build is silently unreachable |
No --json, no receipt, no run history, no environment selection, no exit-code contract | The CLI cannot participate in the operation path this page describes, and an agent or CI job cannot consume it |
Fixing these individually does not converge, because they are one structural fact: two models of what is runnable, and the weaker one is in front.
Move the generic half of the kernel
The CLI cannot become a kernel host while the kernel is closed product code. Split modules/kernel along the boundary this page already states — reusable mechanism to open source, product recipes and rules to the workspace backend.
Moves to reaktor-workspace — open source | Stays in modules/kernel — closed |
|---|---|
| Catalog model and discovery composition; targets, tasks, resources, environments | BestBuds task recipes and connection probes: the database helpers, heimdall.sql, Supabase, k3s and Wrangler checks |
| The effect and approval mechanism: classification interface, plan preparation, fingerprint binding, confirmation and second-approver enforcement, environment gating | The classifications and rules themselves: which task carries which effect, its phrases, its environments and its redactions |
Plan, run and receipt lifecycle; the run ledger and its persistence, implementing the RunLedger port that ToolingPorts.kt declares and nothing implements | Database Studio and auth directory sessions, and the inspector-role policy |
| The selector grammar shared by every host | The product's MCP read selection |
reaktor-workspace sits between reaktor-tooling and the hosts. modules/kernel becomes a catalog contributor registered through an SPI rather than the backend itself, and the layer map gains one open-source row without changing its shape:
Desktop window Terminal Agent client IDE client
Compose / engine reaktor MCP broker thin client
│ │ │ │
└────────────────────┴─────────┬─────────┴───────────────────┘
▼
reaktor-workspace
catalog · effects · environments · approvals · ledger
│ │
▼ ▼
reaktor-graph-runtime reaktor-tooling
│
modules/kernel ──────┘
product recipes, rules and sessions
This is the open-source independence requirement, not a preference: the command line must discover, plan and run in a fresh public clone with no BestBuds checkout present.
What the CLI owns
The CLI is a host. It renders and collects; it decides nothing about tasks.
| Owns | Meaning |
|---|---|
| Parsing | Command tree, flag grammar, shell completions, help |
| Resolution | Turning typed input into exactly one catalog id, and disambiguating when several match |
| Rendering | One result object, two renderers: Mordant for an operator, JSON for a machine |
| Interaction | Pickers, prompts, confirmation-phrase entry, progress, and Ctrl-C mapped to run cancellation |
| Session | Selected workspace, environment and principal, persisted per workspace |
| Approval collection | Presenting the exact plan and capturing the operator's decision |
| Itself | Install, build identity, update, and reporting when the installed build is behind its recorded source |
| Does not own | Where it belongs |
|---|---|
| What counts as a target or a task | Workspace discovery |
| Effect classification | The catalog |
| Approval policy: what is gated, its phrases, its second approver, its environments | The control plane |
| Argument-vector assembly, definition seals, fingerprints, supervision and redaction | reaktor-tooling |
| Run persistence and receipt shape | The run ledger |
| Any product name, path or convention | Catalog data or declared workspace configuration |
Testable form: the CLI source contains no keyword classifier and no product identifier. A dependency and source check in its build, matching verifyToolingBoundary and verifyKernelBoundary, enforces the second half.
Command surface
One identifier space. Every runnable thing is addressed by its catalog id — deploy/web/production, database/memgraph/health, npm/test:unit — so no operator has to know whether a name is a target, a store, a script family or a Gradle module.
| Command | Operation path step | Behaviour |
|---|---|---|
reaktor | discover | Workspace, selected environment, recent runs and what needs attention |
reaktor ls [selector] | inspect | Catalog entries matching a selector, grouped by domain |
reaktor show <id> | inspect | Effect, allowed environments, provenance, approval requirements, last run |
reaktor plan <id> | seal and plan | Prints the plan fingerprint and redacted command; creates no run |
reaktor run <id> | approve and execute | --approve <fingerprint>, and --confirm/--second-approver where the plan declares them |
reaktor runs [<id>] | reconcile and receipt | Run history, or one redacted receipt |
reaktor logs <run-id|id> | reconcile | Streams a live run, or tails a target's logs |
reaktor env [<name>] | — | Shows or selects this workspace's environment |
reaktor check | — | Health, scoped to the providers this workspace's catalog actually uses |
reaktor serve | — | The headless workspace host, replacing reaktor-workbench mcp |
reaktor new, reaktor add, reaktor self | — | Scaffolding, module guidance, and the CLI itself |
Selectors replace generated subcommands. One grammar, used identically by the CLI, the desktop's pane filters and the MCP catalog reads:
reaktor ls domain:deploy env:production
reaktor ls target:appWeb
reaktor ls effect:>=NonProductionWrite
reaktor ls status:unavailable
reaktor ls 'deploy/**'
Shorthand is resolution, not registration. reaktor appWeb deploy and reaktor deploy appWeb both resolve by exact id, then unique catalog match, then an interactive picker, then an error listing candidates. Because no command object is minted per project noun, a target cannot be shadowed by a built-in verb, and a declared target needs no folder on disk to be reachable.
Retired by this design: the per-target, per-store, per-module, per-family and hardcoded-script command generators, the CLI's own effect classifier, its Topology conventions table, and its copies of the argument-redaction helpers — which are promoted to public API in reaktor-tooling, where the same code already exists as a private function.
Contracts
Output. --json writes exactly one JSON value to stdout and moves operator chrome to stderr; --quiet leaves only that value. The JSON is the workspace catalog, plan, run and receipt schema, versioned with reaktor-workspace — not a CLI-specific shape. It is the command line's public compatibility surface.
Exit codes. A script must be able to tell a failed operation from a refused one.
| Code | Meaning |
|---|---|
| 0 | Succeeded |
| 1 | The operation ran and failed |
| 2 | Usage error |
| 64 | No workspace, or the catalog is unavailable |
| 69 | Task unavailable, or not permitted in the selected environment |
| 70 | The plan changed after review; the fingerprint no longer matches |
| 77 | Approval required and not supplied, or refused |
| 124 | Timed out |
| 130 | Cancelled |
Receipts. Every mutating command returns the same redacted receipt the MCP reads return and the desktop displays: run identity, task, environment, status, exit code, timings, approver, independent approver, artifacts, and events with sensitive output withheld.
Environment. --env on every command, defaulting to the session selection and validated against the catalog. The existing rule that a write above LiveRead refuses to run in local becomes expressible from a terminal.
One ledger. Today the CLI, headless MCP host and GUI use separate default state files, so a run started in a terminal is invisible to the desktop's history. A host should attach to a running workspace host when one is present on loopback and open its own otherwise, stating which it did. That replaces per-session persistence with one shared ledger, and it is the same attachment mechanism a future IDE client needs.
Sequencing
| Phase | Scope |
|---|---|
| 0 — honesty | Build-stamped version and a staleness check against the recorded source; --json and the exit-code contract on read commands; check reports rather than fails for providers the workspace does not use; declared targets union into the project model so a scaffolded project is visible; delete the unused ServiceShortcut; promote the redaction helpers into reaktor-tooling and delete the copies; move product names into declared configuration; owner-only permissions on the stored token. No extraction required, and nothing here is discarded by phase 1. |
| 1 — one model | Extract reaktor-workspace; the CLI reads the operational catalog; delete its project heuristics and its classifier; ids, selectors, plan/run/runs; retire reaktor-workbench, moving its supervised-process end-to-end suite with it; add reaktor-cli to the framework Gradle build, which today does not include it, so the root check runs neither of its verification tasks. |
| 2 — one host | reaktor serve as the workspace host; an MCP execution broker over the same plan, approval and run contracts instead of reads only; attachment and one shared ledger across terminal and desktop. |
| 3 — reach | Remote authenticated sessions, incremental context, further provider adapters and IDE clients — the future work already named at the end of this page. |
Evidence required
| Claim | Evidence |
|---|---|
| Independent public command line | Fresh clone of the public framework with no BestBuds checkout discovers, plans and runs a task in a scaffolded project |
| One schema across hosts | One selector string and one receipt schema accepted and produced identically by the CLI, the desktop panes and the MCP reads, against a shared fixture |
| No ambient mutation path | run without approval on a gated task exits 77; a plan altered between plan and run exits 70; sensitive output reaches neither stdout, the ledger nor an agent response |
| One ledger | A run started in the terminal appears in the desktop's history and in control_plane_snapshot without a restart |
| Build identity | reaktor --version reports version, commit and build date, and reaktor check reports when the installed build is behind its source |
| Greenfield coherence | reaktor new produces a project that reaktor, reaktor ls and reaktor check describe completely, with no folder-layout precondition |
Open decisions
- Module boundary. A separate
reaktor-workspace, or growingreaktor-toolingto hold the catalog and control plane. A separate module keeps the tooling boundary narrow — contracts and execution only — and gives the control plane its own dependency budget. This design assumes the separate module. - Attachment default. Whether the CLI attaches to a running host by default or on request. Attaching by default produces one ledger without ceremony but makes terminal behaviour depend on whether a window is open; either way the output must state which host answered.
- Task ids as a compatibility surface. Ids will appear in scripts, CI and agent prompts. Either version them with the catalog schema, or publish an alias mechanism for renames.
- Approval under
--json. A gated task needs an operator and a machine invocation has none. Proposed:--jsonnever prompts, and exits 77 with the plan in its payload for the caller to re-invoke with--approve.