Skip to main content
Version: Development

Set up your development workspace

Create the place where your capsule will be compiled and run. Your application files stay in your project folder; the node, compiler and credentials use separate private storage. First get the developer tools.

Save the setup file for your OS to Downloads. It prepares the existing dev commands, installs your selected tools and reads the template settings for you. You only choose your language, project and workspace; you do not assemble JSON files or copy hashes. Keep this terminal open for the capsule tutorial.

The default project is Projects/My greeting in your home folder, the workspace is test-my-greeting, and the node uses port 18080.

If you use Windows​

Use PowerShell on Windows x86-64 with working WSL2 and virtualization. Your editor and commands stay on Windows; the compiler and node run in the LSF-owned distro.

View the setup script
param(
[ValidateSet('rust','c','typescript','go','java','dotnet')][string]$Language = 'rust',
[ValidatePattern('^test-[a-z0-9][a-z0-9-]*$')][string]$Workspace = 'test-my-greeting',
[string]$Project = (Join-Path $env:USERPROFILE 'Projects\My greeting'),
[ValidateRange(1024,65535)][int]$Port = 18080,
[string]$Inputs = (Join-Path $env:USERPROFILE 'LSF-inputs-alpha5'),
[string]$State = (Join-Path $env:LOCALAPPDATA 'LatentDev-tutorial'),
[switch]$Provision,
[switch]$SessionOnly
)
$ErrorActionPreference = 'Stop'
$Frontend = Join-Path $Inputs 'frontend/bin/latent-dev.exe'
$WindowsVerifier = (Get-Command gh.exe).Source
$WindowsVerifierSha256 = 'sha256:' + (Get-FileHash -LiteralPath $WindowsVerifier -Algorithm SHA256).Hash.ToLowerInvariant()
$LinuxVerifierSha256 = 'sha256:' + (Get-FileHash -LiteralPath (Join-Path $Inputs 'gh-linux') -Algorithm SHA256).Hash.ToLowerInvariant()
$DeveloperPolicy = Join-Path $Inputs 'developer-policy.json'
$RuntimePolicy = Join-Path $Inputs 'native-policy.json'
$TrustedRoot = Join-Path $Inputs 'trusted_root.jsonl'
$Policy = Get-Content -LiteralPath $DeveloperPolicy -Encoding UTF8 | ConvertFrom-Json
$Version = $Policy.version

function dev {
param([Parameter(ValueFromRemainingArguments = $true)][string[]]$DevArguments)
& $Frontend --state-root $State dev @DevArguments
if ($LASTEXITCODE -ne 0) { throw 'The command failed. Inspect its output before continuing.' }
}

if ($SessionOnly) { return }
dev doctor
$Admission = if ($Language -in @('rust','c')) { 'trusted-local' } else { 'signed-fixture' }
New-Item -ItemType Directory -Force -Path (Split-Path -Parent $Project) | Out-Null

$WslBundle = (dev acquire --bundle-directory (Join-Path $Inputs 'wsl') `
--publisher-policy $DeveloperPolicy --trusted-root $TrustedRoot `
--verifier $WindowsVerifier --verifier-sha256 $WindowsVerifierSha256 `
--version $Version --target linux-x86_64-wsl-rootfs --allow-candidate | ConvertFrom-Json).result
$WslInventory = Get-Content -LiteralPath (Join-Path $State "bundles\$($WslBundle.bundle)\rootfs-inventory.json") `
-Encoding UTF8 | ConvertFrom-Json
if ($Provision) {
dev provision --bundle $WslBundle.bundle --consent-provision
} else {
dev wsl-status
}

dev wsl-workspace --workspace $Workspace --helper-sha256 $WslInventory.helperSha256
$Utf8 = [Text.UTF8Encoding]::new($false)
$RuntimeInputs = Join-Path $Inputs "$Workspace-runtime.json"
$ToolInputs = Join-Path $Inputs "$Workspace-$Language-tools.json"
$Common = @{
version = $Version; trustedRoot = $TrustedRoot
verifier = (Join-Path $Inputs 'gh-linux'); verifierSha256 = $LinuxVerifierSha256
allowCandidate = $true; consent = $true
}
$Runtime = $Common.Clone()
$Runtime.schemaVersion = 'latent.dev.install-inputs.v1'
$Runtime.releaseDirectory = Join-Path $Inputs 'native'
$Runtime.publisherPolicy = $RuntimePolicy
$Runtime.profile = 'local-experimental-v1'
$Runtime.port = $Port
$Tools = $Common.Clone()
$Tools.schemaVersion = 'latent.dev.tool-inputs.v1'
$Tools.bundleDirectory = Join-Path $Inputs $Language
$Tools.publisherPolicy = $DeveloperPolicy
$Tools.language = $Language
[IO.File]::WriteAllText($RuntimeInputs, ($Runtime | ConvertTo-Json), $Utf8)
[IO.File]::WriteAllText($ToolInputs, ($Tools | ConvertTo-Json), $Utf8)
dev install --workspace $Workspace --runtime-inputs $RuntimeInputs
dev install-tools --workspace $Workspace --tool-inputs $ToolInputs

$Templates = (dev acquire --bundle-directory (Join-Path $Inputs $Language) `
--publisher-policy $DeveloperPolicy --trusted-root $TrustedRoot `
--verifier $WindowsVerifier --verifier-sha256 $WindowsVerifierSha256 `
--version $Version --target linux-x86_64 --allow-candidate | ConvertFrom-Json).result
$Bundle = $Templates.bundle
$Index = Get-Content -LiteralPath (Join-Path $State "bundles/$Bundle/templates.json") -Raw | ConvertFrom-Json
$GreetingTemplate = $Index.templates.greeting.identity
$WordCountTemplate = $Index.templates.'word-count'.identity
$ShippingTemplate = $Index.templates.shipping.identity
Write-Host 'Workspace ready. Continue with Creating a capsule in this terminal.'

For your first workspace, run this in PowerShell, choosing your downloaded language:

. "$HOME/Downloads/setup-lsf-workspace.ps1" -Language rust -Provision

The leading dot loads the dev shortcut and project settings into this terminal. -Provision creates the shared LSF distro and leaves other distros alone. For later workspaces, omit -Provision to reuse it.

If you use Linux​

Use Ubuntu 24.04 x86-64, Linux 6.8 or newer, and Python 3.13.5 at /usr/local/bin/python3.13. Run as your ordinary account. Your host administrator supplies this interpreter; the downloaded frontend contains the helper.

View the setup script
set -euo pipefail
umask 077
export Inputs="${LSF_INPUTS:-$HOME/LSF-inputs-alpha5}"
export Language="${1:-rust}"
export Workspace="${2:-test-my-greeting}"
Project="${3:-$HOME/Projects/My greeting}"
export Port="${4:-18080}"
case "$Language" in rust|c|typescript|go|java|dotnet) ;; *) echo 'Choose a supported language.' >&2; return 2;; esac
[[ "$Workspace" =~ ^test-[a-z0-9][a-z0-9-]*$ ]] || { echo 'Use a test- workspace name.' >&2; return 2; }
[[ "$Port" =~ ^[0-9]+$ ]] && ((Port >= 1024 && Port <= 65535)) || { echo 'Choose a port from 1024 to 65535.' >&2; return 2; }
Frontend="$Inputs/frontend/bin/latent-dev"
State="${LSF_STATE:-$HOME/.latent-dev-tutorial}"
Admission=signed-fixture
if [[ "$Language" == rust || "$Language" == c ]]; then Admission=trusted-local; fi
mkdir -p "$(dirname "$Project")"
python3 - <<'PY'
import hashlib, json, os, pathlib, shutil
root = pathlib.Path(os.environ['Inputs'])
language = os.environ['Language']
workspace = os.environ['Workspace']
assert language in ('rust', 'c', 'typescript', 'go', 'java', 'dotnet')
manifest = json.loads((root/'linux/developer-bundle.json').read_text())
helper = next(file for file in manifest['files'] if file['path'] == 'helper.pyz')
verifier = root/'gh-linux'
shutil.copyfile(shutil.which('gh'), verifier)
verifier.chmod(0o700)
identity = 'sha256:'+hashlib.sha256(verifier.read_bytes()).hexdigest()
def save(name, value):
(root/name).write_text(json.dumps(value)+'\n')
save('direct-backend.json', dict(kind='linux', helperSha256=helper['sha256'],
python='/usr/local/bin/python3.13', helper=str(root/'frontend/helper.pyz')))
common = dict(version=manifest['version'], trustedRoot=str(root/'trusted_root.jsonl'),
verifier=str(verifier), verifierSha256=identity, allowCandidate=True, consent=True)
save(workspace+'-runtime.json', dict(common, schemaVersion='latent.dev.install-inputs.v1',
releaseDirectory=str(root/'native'), publisherPolicy=str(root/'native-policy.json'),
profile='local-experimental-v1', port=int(os.environ['Port'])))
save(workspace+'-tools.json', dict(common, schemaVersion='latent.dev.tool-inputs.v1',
bundleDirectory=str(root/language), publisherPolicy=str(root/'developer-policy.json'), language=language))
PY
dev() { "$Frontend" --state-root "$State" dev "$@"; }
dev doctor
dev connect --workspace "$Workspace" --backend-config "$Inputs/direct-backend.json"
dev install --workspace "$Workspace" --runtime-inputs "$Inputs/$Workspace-runtime.json"
dev install-tools --workspace "$Workspace" --tool-inputs "$Inputs/$Workspace-tools.json"

Version=$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["version"])' "$Inputs/developer-policy.json")
VerifierIdentity="sha256:$(sha256sum "$Inputs/gh-linux" | cut -d' ' -f1)"
dev acquire --bundle-directory "$Inputs/$Language" \
--publisher-policy "$Inputs/developer-policy.json" --trusted-root "$Inputs/trusted_root.jsonl" \
--verifier "$Inputs/gh-linux" --verifier-sha256 "$VerifierIdentity" \
--version "$Version" --target linux-x86_64 --allow-candidate > "$Inputs/$Workspace-templates.json"
Bundle=$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["result"]["bundle"])' "$Inputs/$Workspace-templates.json")
GreetingTemplate=$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["templates"]["greeting"]["identity"])' "$State/bundles/$Bundle/templates.json")
WordCountTemplate=$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["templates"]["word-count"]["identity"])' "$State/bundles/$Bundle/templates.json")
ShippingTemplate=$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["templates"]["shipping"]["identity"])' "$State/bundles/$Bundle/templates.json")
echo 'Workspace ready. Continue with Creating a capsule in this terminal.'

Run this in Bash, choosing your downloaded language:

source "$HOME/Downloads/setup-lsf-workspace.sh" rust

source loads the dev shortcut and project settings into this terminal. Two workspaces under the same Linux account do not protect data from that account; use separate accounts when that matters.

Continue with your application​

Expect successful installation results followed by Workspace ready. The node has not started yet. Open Creating a capsule to create the project, build it, start the node and send your first request. The remaining development commands are shared across Windows and Linux.

Rust and C use the explicit trusted-local profile for your disposable development code. The other languages use signed-fixture with their compiler's required runtime permissions. This fixture approves one build for 30 minutes; a changed build or expired fixture needs a fresh disposable workspace. The edit guide explains this and the supported automatic watch loop.

Set up another project​

Reuse the same downloads. Choose a different workspace and project; use a different port if both nodes will run together. For a word counter, on Windows:

. "$HOME/Downloads/setup-lsf-workspace.ps1" -Language rust -Workspace test-my-words -Project "$HOME/Projects/My words" -Port 18081

Or on Linux, the arguments are language, workspace, project and port:

source "$HOME/Downloads/setup-lsf-workspace.sh" rust test-my-words "$HOME/Projects/My words" 18081

If you chose a custom download directory, supply -Inputs PATH on Windows or set LSF_INPUTS before sourcing the Linux script. Custom private state uses -State or LSF_STATE; keep it outside the project and let the frontend create it.

For an occupied port, choose another port for a new workspace. If setup fails, inspect the reported error before running more commands. A partial WSL import needs dev wsl-status and the documented recovery path; do not repeat an uncertain operation. See Developer commands.

Use a remote Linux host instead​

The administrator supplies a dedicated unprivileged account, its private SSH identity file, and an independently verified known-hosts file. The remote helper must be installed at /opt/latent-dev/helper.pyz, with Python 3.13.5 at /usr/local/bin/python3.13. Keep the private identity outside the application source. An unknown or changed host key fails before the helper runs.

On Windows, this reviewed backend selection uses the Windows OpenSSH client:

{
"kind": "ssh",
"helperSha256": "sha256:REPLACE_WITH_SELECTED_HELPER_DIGEST",
"host": "dev.example.test",
"port": 22,
"user": "lsfdev",
"identityFile": "C:\\LSF Private\\id_ed25519",
"knownHosts": "C:\\LSF Private\\known_hosts",
"ssh": "C:\\Windows\\System32\\OpenSSH\\ssh.exe"
}

Replace the example host, account and paths with the supplied ones. Save it outside the project and connect explicitly:

& $Frontend --state-root $State dev connect --workspace test-remote-greeting `
--backend-config 'C:\LSF Private\ssh-backend.json'

Then use the same install, install-tools, init, trust and application commands with that workspace name. The input documents refer to files on the client; the controller transfers only the selected bytes before verifying/installing on the remote host. Node management binds the remote loopback interface. Client loopback is a different network namespace; no public management bind or tunnel is assumed.

On a Linux client, select /usr/bin/ssh and absolute Linux identity/known-hosts paths in the same JSON schema. SSH uses strict host-key checking, the one selected identity, no agent forwarding, no proxy command and finite connection deadlines. A lost SSH connection leaves remote cleanup unconfirmed: restore the connection, inspect status and recover the original pending operation. Do not repeat an uncertain invocation.

dev down retains the remote workspace. dev purge --confirm-workspace NAME removes only that owned workspace after confirmed cleanup; it does not remove the remote account, SSH server, host, or another workspace. The optional devcontainer is another client for this same adapter.