Skip to main content

Splinter → Human.work — Features Porting Plan

Author: Alex Dankov (@Dankovk) Source: Notion — Splinter → Human.work Features Porting (2026-05-20) Status: Architectural target and reasoning for what gets ported from Splinter (now Rig — humanity-org/rig) into Human.work, and what does not. Companion docs:

Port Splinter's control-plane discipline, not Splinter as a whole product. End-state architecture is five layers: control plane / runtime manifest / capability shell / OS sandbox / inspector — see ISOLATION_SANDBOXING_BINARIES.md for the deep-dive on layers 2–4.

Cross-ref: the layered isolation model here is the runtime/sandbox half of the broader resource-class isolation matrix defined in ADR-020 — Isolation Classification. ADR-020 is the source of truth for which resources isolate by Org-only vs (Org × Specialist) vs Specialist-only; this porting plan is how the (Org × Specialist) runtime tier gets built.


Purpose

This document describes which Project Splinter architecture pieces are worth porting into Human.work, why they are worth porting, and what the resulting architecture should look like. This is not a roadmap. It is the architectural target and reasoning. The core idea is simple: port Splinter’s control-plane discipline, not Splinter as a whole product. Splinter contains strong patterns for controlled execution, policy enforcement, event timelines, approvals, artifacts, runtime inspection, and sandboxing. Those patterns fit Human.work. Splinter’s desktop/TUI surfaces and code-agent PR machinery mostly do not.

High-level position

Human.work should become safer and more observable before it becomes more autonomous. The useful Splinter pieces are:

  • explicit run state;
  • durable runtime events;
  • approval and user-input requests;
  • a unified tool gateway;
  • policy/guard evaluation;
  • structured artifacts;
  • runtime/operation inspection;
  • controlled execution through a capability shell;
  • OS/container sandboxing around that shell;
  • validation pipelines and plugin validators. The wrong port is:
  • raw shell access;
  • broad file tools for normal Specialist workflows;
  • desktop/TUI cockpit;
  • worktree/PR machinery as the default execution model;
  • autonomous scheduling before approval/policy/event primitives exist.

Why Splinter is relevant

Splinter is built around the idea that autonomous or semi-autonomous agents need a control plane. It treats agent execution as something that must be constrained, observed, replayed, approved, and validated. That matters for Human.work because Specialist behavior touches client data, client communications, integrations, credentials, and external channels. The platform needs to answer hard operational questions:

  • What did the model try to do?
  • Which tool was requested?
  • Was the tool allowed, blocked, or approval-gated?
  • Which policy rule matched?
  • What data was accessed?
  • Was anything sent externally?
  • Who approved it?
  • What artifact/log proves the result?
  • Which runtime/policy/tool manifest was active?
  • Why did a task get stuck or fail? Splinter has patterns for those questions. Human.work should adapt them to its product model.

End-state architecture

The target architecture has five layers.

Human.work control plane
owns scope, credentials, policy, approvals, audit, events, artifacts, tool dispatch

Runtime manifest / compiled runner
carries allowed operation surface, policy hash, tool manifest hash, runtime identity

Capability shell
executes structured non-secret operations: file/artifact/process/browser primitives

OS/container sandbox
enforces filesystem, syscall, process, network, memory, and time boundaries

Ops/Expert inspection surface
shows runs, events, approvals, denied operations, artifacts, runtime health

Implementation status (last updated 2026-05-28): Layer 1 control plane and Layer 5 inspection are landed; Layers 2–4 are now locally scaffolded in this branch (compiled-runner workflow/registry, Zig capability shell + Python adapter, ADR-008 substrate scaffold/nightly deny-battery) but production closure still needs CI secret configuration, ADR cosign, merge, and nightly soak evidence.

The important separation is authority vs execution.

  • The control plane decides what is allowed.
  • The runner proves what it is allowed to execute.
  • The capability shell performs narrow structured operations.
  • The sandbox enforces hard boundaries.
  • The inspector makes all of it visible.

Architectural flow

Client message or internal task

Run is created

Control plane resolves policy, tool manifest, runtime manifest

Model proposes message/tool/action

Control plane evaluates action

If action needs approval:
create approval request and pause

If action is a credentialed business tool:
control-plane tool gateway performs/proxies call

If action is file/process/browser-like:
capability shell executes structured operation

OS/container sandbox enforces hard limits

Events, artifacts, audit records are written

Expert/Ops surfaces show timeline and state

Secrets stay in the control plane or dedicated credential systems. The execution runtime receives minimal capabilities, not ambient credentials.

Splinter part: run state machine

What Splinter has

Splinter defines explicit run/runtime statuses in packages/contracts/src/runtime.ts. It separates runs, runtimes, approvals, user input requests, and worktrees. Representative statuses:

  • created
  • queued
  • preparing
  • running
  • waiting-approval
  • waiting-user-input
  • validating
  • reviewing
  • completed
  • failed
  • cancelled
  • paused

Why port it

Without a canonical run state, every feature invents a partial state machine: conversation status, Expert queue status, tool execution status, agentic task status, channel delivery status. That makes the platform harder to debug and easier to break. A run state machine gives one authoritative representation of execution.

How to port it

Create a Human.work run model that captures each meaningful model/tool/task execution. Suggested concept: agent_runs. Important fields:

  • run id;
  • conversation/task references;
  • Specialist/runtime references;
  • run kind;
  • status;
  • model/runtime adapter;
  • runner hash;
  • policy hash;
  • manifest hash;
  • pending approval count;
  • pending input count;
  • timestamps;
  • error text. Adapt status names to Human.work terminology where useful:
  • waiting_expert_approval instead of generic waiting-approval;
  • waiting_client_input instead of generic waiting-user-input;
  • tool_executing where a tool call is in progress.

End architecture

Every Specialist response, tool action, and internal agentic execution has a run. Runs are visible in Ops and can be replayed from events. Queue items and approvals refer to runs instead of duplicating runtime state.

Splinter part: durable event bus

What Splinter has

Splinter has an append-only event bus in packages/runtime/src/events.ts. Events are written as JSONL and retained as execution evidence.

Why port it

Human.work needs a durable execution timeline. Audit logs are necessary but not enough. Operators need a chronological view of what happened inside a run. Events are the connective tissue between model calls, tool calls, approvals, sandbox decisions, artifacts, and final outcomes.

How to port it

Use a database-backed append-only event table rather than local JSONL. Suggested concept: run_events. Event envelope:

{
"id": "evt_...",
"runId": "run_...",
"type": "tool.requested",
"source": "api|agent|runner|sandbox|expert|system",
"actor": { "type": "specialist|expert|runtime|system", "id": "..." },
"correlationId": "...",
"payload": {},
"createdAt": "..."
}

Initial event types:

  • run.created
  • run.started
  • runner.selected
  • runner.policy.loaded
  • model.started
  • model.completed
  • tool.requested
  • tool.allowed
  • tool.blocked
  • tool.started
  • tool.succeeded
  • tool.failed
  • approval.requested
  • approval.resolved
  • sandbox.exec.started
  • sandbox.exec.denied
  • sandbox.exec.finished
  • artifact.created
  • draft.created
  • message.sent
  • run.completed
  • run.failed

End architecture

Every meaningful runtime action emits a durable event. Ops and Expert surfaces render timelines from these events. Event payloads are masked and never contain raw secrets.

Splinter part: approvals and input requests

What Splinter has

Splinter models approvals and user-input requests as first-class runtime objects. A run can pause until approval or input is resolved.

Why port it

Human.work is fundamentally HITL. Approval should be a platform primitive, not an incidental field inside a task JSON blob or queue item. Approval is needed for:

  • high-risk replies;
  • sending messages externally;
  • integration writes;
  • data exports;
  • risky browser actions;
  • tool calls with irreversible side effects;
  • missing context questions.

How to port it

Create first-class approval and input request models. Approval request fields:

  • request id;
  • run id;
  • request kind;
  • risk level;
  • status;
  • payload;
  • requested by;
  • resolved by;
  • resolved at;
  • expiration;
  • created at. Decision states:
  • pending
  • approved
  • rejected
  • cancelled
  • expired

End architecture

A run can pause for explicit approval or input. Experts and Ops see pending approvals in a single inbox. Resolving an approval resumes or blocks the run and emits events/audit records.

Splinter part: policy guard

What Splinter has

Splinter has a PolicyGuard that evaluates several action types through a unified decision engine: command execution, tool calls, file access, content writes.

Why port it

Human.work currently needs consistent decisions across business actions. Tool permissions, risk classification, auto-reply policy, integration credentials, and approvals should not be scattered across unrelated if-statements. A single policy evaluator gives predictable, auditable decisions.

How to port it

Implement a Human.work-specific ActionPolicyService. Evaluation contexts:

  • tool-call
  • message-send
  • integration-read
  • integration-write
  • data-export
  • credential-access
  • channel-send
  • artifact-read
  • artifact-write
  • exec-argv
  • browser-action Decision values:
  • allow
  • observe
  • require_expert_approval
  • require_client_approval
  • block Policy rule examples:
  • block cross-scope data access;
  • require Expert approval for high-risk replies;
  • require approval for integration writes;
  • block raw credential exposure;
  • block production infrastructure mutation without explicit approval;
  • observe new tool integrations before enforce mode;
  • require artifacts for browser smokes;
  • block unredacted PII in event/audit payloads.

End architecture

Every risky action has a policy decision. The decision is emitted as an event, stored in audit, and shown in run timelines. Policy can run in observe mode before enforce mode.

Splinter part: tool gateway

What Splinter has

Splinter uses a runtime tool gateway/materialization pattern. Tools are not random functions scattered through the system; they have descriptors and a controlled execution path.

Why port it

Human.work already has multiple tool planes: API registry, legacy tool registry, Python agent tool specs. This will drift as integrations grow. The platform needs a single source of truth for:

  • tool names;
  • actions;
  • schemas;
  • risk levels;
  • credential requirements;
  • integration type;
  • policy subject;
  • dispatch handler;
  • audit behavior.

How to port it

Make the Nest/API tool registry canonical. Generate or contract-test Python agent tool specs against it. Tool gateway pipeline:

request
→ schema validation
→ scope check
→ permission check
→ policy decision
→ approval if needed
→ credential resolution or proxy
→ execution
→ result normalization
→ audit
→ event
→ metrics

Vendor/integration calls should go through this gateway:

  • Nango;
  • Jumio;
  • Shopify;
  • Amazon;
  • future CRM/accounting/KYC tools.

End architecture

The model can only request tools that the platform advertises. The API validates and dispatches every tool call. Python specs cannot drift from API dispatch. Tool results and failures are normalized.

Splinter part: artifacts

What Splinter has

Splinter defines artifact summaries tied to runs/tasks. Artifacts represent generated outputs, logs, screenshots, validation evidence, and other durable evidence.

Why port it

Human.work needs durable evidence for Expert review, Ops debugging, compliance, and smoke tests. Raw logs are not enough. Artifacts are especially important for:

  • prompt snapshots;
  • model outputs;
  • redacted tool input/output;
  • generated documents;
  • browser screenshots/traces;
  • validation logs;
  • sandbox execution logs.

How to port it

Create a run_artifacts model backed by object storage for large blobs. Artifact fields:

  • artifact id;
  • run id;
  • task/conversation reference;
  • kind;
  • label;
  • storage URL/path;
  • metadata;
  • redaction level;
  • created at.

End architecture

Runs link to artifacts. Artifacts can be inspected from Ops/Expert surfaces. Sensitive artifacts are redacted or permission-gated.

Splinter part: plugin validators

What Splinter has

Splinter has a plugin validator system in packages/runtime/src/plugins.ts. Validators run against tasks and emit started/finished events.

Why port it

Human.work needs repeatable guardrails that catch known classes of mistakes:

  • tool registry drift;
  • missing org scoping;
  • audit PII leakage;
  • bad env variable names;
  • Nango key-tier mistakes;
  • test weakening;
  • terminology errors;
  • unsafe integration credential handling. These are better handled by validators than by relying on memory and code review alone.

How to port it

Implement a small validator interface:

interface Validator {
id: string;
appliesTo(context): boolean;
run(context): Promise<ValidationResult>;
}

Initial validators:

  • registry drift validator;
  • IDOR/org-scope validator;
  • audit masking validator;
  • Nango environment/key-tier validator;
  • test integrity validator;
  • terminology validator;
  • legacy route validator.

End architecture

Validators run in CI and scheduled health checks. Their results are recorded as validation events/artifacts and visible in Ops.

Splinter part: inspector

What Splinter has

Splinter has inspector tools and journals for runtime inspection, follow-up notes, active runs, and operational debugging.

Why port it

Human.work needs internal Ops visibility. Operators should not need Railway logs to understand a stuck Specialist run or failed tool call.

How to port it

Do not port the TUI/desktop surface. Build inside /ops. Suggested Ops surfaces:

  • active runs;
  • run detail timeline;
  • pending approvals;
  • failed tools;
  • denied sandbox operations;
  • artifacts;
  • integration health;
  • runner/policy/manifest hashes;
  • runtime status.

End architecture

AMs/SuperAdmins can inspect runtime behavior directly in the product. Run timelines and artifacts become the primary debugging interface.

Splinter part: validation queue

What Splinter has

Splinter has queue/scheduler machinery for validating and verifying tasks.

Why port it

Human.work needs scheduled and on-demand validation, but not necessarily autonomous code-agent scheduling. Useful validation jobs:

  • Nango health;
  • Jumio sandbox;
  • channel webhook smoke;
  • Expert workspace browser smoke;
  • client onboarding smoke;
  • agent eval calibration;
  • integration credential drift;
  • provider config checks.

How to port it

Create validation runs rather than generic code-agent queues. Validation runs produce:

  • status;
  • events;
  • artifacts;
  • failures;
  • follow-up tasks.

End architecture

Integration and UI smokes run periodically. Failures show up in Ops with artifacts and remediation notes.

Splinter part: sandboxing and isolation

What Splinter has

Splinter has sandbox backend abstraction and runtime isolation ideas. It can select sandbox backends, wrap runtime commands, and reason about filesystem/network policy.

Why port it

Human.work will eventually need safe execution for file processing, scripts, browser automation, and internal Ops/code tasks. But normal Specialist conversation should not get arbitrary shell access. Sandboxing is useful only when paired with:

  • structured operations;
  • no ambient secrets;
  • policy decisions;
  • event logging;
  • approval gates;
  • OS/container enforcement.

How to port it

Use a layered sandbox model:

  1. control-plane policy;
  2. runtime manifest;
  3. capability shell;
  4. OS/container sandbox;
  5. events/artifacts/inspection. Do not expose a general shell. Expose typed operations.

End architecture

File/process/browser work runs inside a restricted runtime. The model cannot access arbitrary filesystem/network/secrets. Denied operations are visible and auditable.

Per-runtime compiled runner

Why compile/bundle a runner

A compiled or bundled runner reduces the execution surface and improves provenance. Benefits:

  • only allowed operations are present;
  • policy/tool manifest can be baked or signed;
  • no source tree or dev tooling by default;
  • runner hash is auditable;
  • stale/unknown runners can be rejected;
  • runtime identity is explicit.

What compilation does not solve

Compilation alone does not:

  • enforce syscalls;
  • prevent network exfiltration;
  • protect env secrets;
  • enforce database scoping;
  • make arbitrary shell safe;
  • replace the control plane.

What to bake in

Bake in:

  • environment;
  • runner version;
  • allowed tool manifest;
  • allowed operation manifest;
  • policy hash;
  • prompt/spec bundle hash;
  • event schema version;
  • control-plane verifier/public key;
  • build provenance. Do not bake in:
  • vendor secrets;
  • OAuth tokens;
  • DB credentials;
  • signing keys;
  • master encryption keys;
  • infrastructure tokens.

End architecture

A run records exactly which runner build was used. The control plane rejects stale or unknown runner builds. Secrets are still fetched/proxied by the control plane.

Zig capability shell

Why Zig

A small native runtime can be easier to reason about than a full Python/Node shell environment. Zig is suitable for a minimal capability shell because it can produce a compact binary with explicit process/file/network handling. The point is not that Zig is magic. The point is that a small runtime with a narrow protocol is easier to constrain than /bin/sh plus a full application source tree.

Shell is the wrong abstraction

The model should not get shell strings. It should get operation schemas. Supported operations should look like this:

{ "op": "artifact_read", "path": "inputs/customer.csv", "maxBytes": 1048576 }
{ "op": "artifact_write", "path": "outputs/result.json", "contentRef": "blob:..." }
{
"op": "exec_argv",
"argv": ["python", "scripts/normalize.py", "inputs/customer.csv"],
"cwd": "/workspace",
"network": "off",
"timeoutMs": 30000,
"maxOutputBytes": 262144
}

Capability shell responsibilities

The shell should enforce:

  • JSON operation schema;
  • deny by default;
  • path allowlists;
  • argv-only execution;
  • no shell interpolation;
  • env scrubbing;
  • timeout caps;
  • output caps;
  • process tree cleanup;
  • working directory restrictions;
  • network mode declaration;
  • event emission.

End architecture

The runtime can perform useful file/process operations without giving the model a general shell. Every operation is structured, constrained, and auditable.

No-env secrets policy

Why env secrets are dangerous

Environment variables are ambient authority. Child processes can often read them through:

  • env;
  • /proc/self/environ;
  • process listings;
  • crash dumps;
  • logs;
  • debugging tools. If secrets are in env, arbitrary subprocess execution becomes much more dangerous.

Preferred pattern

Credentialed business tools should execute through the control-plane gateway.

runtime requests tool action
→ control plane checks policy and approvals
→ control plane uses credential
→ control plane returns redacted result

The runtime never sees the raw secret.

Capability token fallback

If direct runtime access is unavoidable, use short-lived capability tokens:

  • scoped to one run;
  • scoped to one action;
  • scoped to one resource;
  • short TTL;
  • signed;
  • passed over stdin/fd/memory;
  • never stored in env;
  • never logged.

End architecture

Runners have no broad ambient credentials. Secrets stay in credential systems or the control plane. Runtime operations receive the minimum authority needed for a single action.

OS/container sandbox

Why Zig is not enough

A Zig runtime can constrain itself and choose how to launch children. It cannot fully prevent child syscalls, arbitrary filesystem access, or network exfiltration unless an OS/container sandbox enforces those constraints.

Enforcement layer

For Linux-style workers, use:

  • namespaces;
  • read-only root filesystem;
  • bind mounts for workspace/artifacts only;
  • tmpfs for temp;
  • seccomp-bpf;
  • cgroups;
  • dropped capabilities;
  • no-new-privileges;
  • network namespace or egress proxy;
  • optional Landlock.

Sandbox modes

Useful modes:

  • read-only
  • workspace-write
  • artifact-write
  • network-off
  • network-proxy-only
  • danger-full-access for explicit dev-only use

End architecture

The capability shell runs inside an enforceable sandbox. Even if a child process misbehaves, the OS boundary prevents broad filesystem/network/process abuse.

Browser automation

Why port carefully

Browser automation is useful for onboarding smokes, integration setup validation, and internal QA. It is also risky because browsers handle credentials and can perform irreversible actions.

How to port

Treat browser sessions as sandboxed, artifact-producing operations. Rules:

  • domain allowlists;
  • no raw credential exposure to model;
  • screenshots/traces stored as artifacts;
  • submit/write actions approval-gated;
  • browser sessions linked to runs;
  • all actions emitted as events.

End architecture

Browser automation is available for controlled Ops/validation workflows first. It is not a default Specialist capability.

Worktree isolation

Why not default

Splinter’s worktree isolation is excellent for code agents. Human.work’s normal Specialist workflows are not code-editing tasks.

How to port

Use worktrees only for internal code/Ops tasks. Runtime modes:

  • conversation
  • ops_workspace
  • ops_worktree

End architecture

Internal code tasks can run in isolated worktrees. Client-facing Specialist conversations remain conversation/tool workflows.

What not to port

Do not port:

  • Splinter desktop/TUI as a Human.work surface;
  • PR/branch/Greptile loop as default workflow;
  • raw filesystem tools for normal Specialists;
  • broad autonomous scheduler before policy/events/approvals;
  • native snapshots before file/code-agent workloads justify them;
  • codebase-specific rules as product policy. Port the architecture patterns, not the entire product.

Final architecture summary

The desired Human.work runtime architecture is:

1. Control plane creates and owns runs.
2. Every execution emits durable events.
3. Risky actions become approval/input requests.
4. Tools execute through one policy-aware gateway.
5. Business credentials stay in the control plane or credential systems.
6. File/process/browser operations go through a structured capability shell.
7. The capability shell receives no ambient secrets.
8. OS/container sandboxing enforces hard boundaries.
9. Artifacts preserve redacted evidence.
10. Ops and Expert surfaces inspect timelines, approvals, artifacts, and failures.

This gives Human.work a controlled agent runtime: observable, approvable, policy-governed, secret-minimized, and eventually sandboxed enough for higher-autonomy internal work.