Skip to content

Workspace Spec

The workspace spec (workspace.yaml) is the kernel-level configuration file that defines the runtime environment for a LumenFlow workspace. It declares which packs are loaded, which lanes exist, and what security boundaries apply.

LumenFlow is designed to work across multiple consumer repo shapes, but the portability contract is explicit:

  • LumenFlow remains a Node-distributed CLI.
  • Consumer repos do not need to be Next.js apps or pnpm monorepos.
  • Framework-specific surfaces such as apps/web dashboards are optional and should only be referenced when present or configured.

Current baseline archetypes for generic lifecycle support:

ArchetypeExpected shapeGeneric lifecycle expectation
Single-package service reposrc/**, no apps/ or packages/init, wu:create, wu:brief, wu:prep, and wu:done work without monorepo-specific guidance
Monorepo without web appmultiple packages or services, no apps/webgate planning and onboarding do not instruct the user to start a dashboard
Backend/docs-oriented reposervice code plus docs/content pathswork classification does not infer UI-only guidance from framework-like filenames alone

Smoke verification for this baseline should confirm:

  1. onboarding does not advertise dashboard startup when no dashboard surface exists
  2. classifier defaults stay backend/docs/infra-oriented unless generic UI signals or workspace config justify UI guidance
  3. docs and generated workflow guidance describe these boundaries explicitly
# workspace.yaml
id: my-workspace
name: My Project

packs:
  - id: software-delivery
    version: '0.1.0'
    integrity: sha256:a1b2c3d4e5f6...
    source: local
    # url: optional — required for git/registry sources
    # registry_url: optional — override default registry

lanes:
  - id: framework-core
    title: 'Framework: Core'
    allowed_scopes:
      - type: path
        pattern: 'packages/framework/**'
        access: write
    wip_limit: 1
    # policy_overrides: optional

security:
  allowed_scopes:
    - type: path
      pattern: '**'
      access: write
    - type: network
      posture: 'off'
  deny_overlays:
    - '.lumenflow/**'

# policies: optional, workspace policy overrides (config:set subkeys)
# control_plane: optional, connection and sync settings (config:set validated subkeys)
# software_delivery: optional, Software Delivery pack configuration (config:set)

The kernel recognizes these workspace root keys:

FieldTypeRequiredManaged ByDescription
idstringYesWorkspace setupUnique workspace identifier
namestringYesWorkspace setupHuman-readable workspace name
packsPackPin[]Yespack:installArray of pack pins to load
lanesLaneSpec[]YesComplete JSON root via config:set; lane:edit for its existing workflowsArray of kernel lane definitions
policiesobjectNoconfig:set / config:unset subkeysWorkspace-level policy overrides
securityobjectYesComplete JSON root via config:set; optional validated deletions via config:unsetWorkspace-level security config
software_deliveryobjectNoconfig:set (pack config)Software Delivery pack configuration (see below)
control_planeobjectNoconfig:set (validated subkeys)Control plane connection and sync settings

Replace the complete kernel lanes array or security object with config:set --json-value. The strict writer rejects partial dotted mutations, unknown keys, malformed scopes, and duplicate lane IDs before effects. Operators supply scopes explicitly; no grants are inferred. config:unset keeps required roots and fields protected. policies subkeys continue through validated config:set and config:unset routes. The top-level id, name, and packs remain managed by workspace setup and pack management. lane:edit retains its existing two-plane behavior; software_delivery.lanes remains pack-owned and is not changed by replacing kernel lanes.

Running pnpm lumenflow --merge on an existing project fills only missing required kernel fields from the maintained workspace defaults. Existing identity, pack pins, top-level lanes, security settings, policies, and pack-owned configuration are preserved. Fresh initialization does not serialize retired kernel namespaces. Existing unowned memory_namespace and event_namespace roots, or security.network_default, are refused before the file is rewritten; init does not silently delete them. Use sequential governed config:unset cleanup first. Pinned pack-owned namespace roots retain their pack ownership. Invalid explicit kernel values also leave workspace.yaml unchanged.

The software_delivery block is still merged with its maintained defaults on each init run; existing gate overrides and delivery lane definitions take precedence. Custom pack config roots are retained in the workspace document. Use --merge for normal existing-project initialization; --force can replace managed workspace content and is not a policy-preserving legacy migration.

Root keys fall into three categories based on how they are modified:

  1. Validated roots and subkeys — lanes and security accept complete JSON root replacement through config:set; control_plane and policies retain validated subkey writes. Optional deletions use config:unset and validate the remaining object.
  2. Pack config keys — Keys like software_delivery are declared by a pack’s config_key field in its manifest. They are written with config:set and validated against the pack’s config schema.
  3. Managed roots — id, name, and packs remain owned by workspace setup and pack management. lanes dotted edits and its existing Software Delivery two-plane workflow remain owned by lane:edit.

When a pack declares a config_key in its manifest, that key becomes a valid root-level field in workspace.yaml. For example, the Software Delivery pack declares config_key: software_delivery, which makes the software_delivery root key available.

The kernel remains domain-agnostic; Software Delivery is one domain pack. The software_delivery field is optional. If the Software Delivery pack is not pinned in the packs array, the workspace boots without it. This allows workspaces that use other domain packs (or no packs at all) to operate with a minimal kernel-only configuration.

Each entry in the packs array is a pack pin — a versioned, integrity-checked reference to a domain pack that the kernel loads at startup.

FieldTypeRequiredDescription
idstringYesPack identifier (e.g., software-delivery)
versionstringYesSemantic version (e.g., 0.1.0)
integritystringYessha256:<64-hex> hash or dev for local development
sourcestringYesOne of local, git, or registry
urlstringNoSource URL (required for git and registry sources)
registry_urlstringNoOverride the default pack registry URL

The integrity field ensures packs cannot be tampered with. It uses the same sha256:<hex> format as Subresource Integrity (SRI):

  • sha256:<64-char-hex> — Production mode. The kernel verifies the pack manifest hash matches before loading.
  • dev — Non-production integrity mode. Production loading rejects it.
packs:
  # Production: integrity-verified
  - id: software-delivery
    version: '1.0.0'
    integrity: sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
    source: registry

  # Development: skip verification
  - id: software-delivery
    version: '0.1.0'
    integrity: dev
    source: local

Each entry in lanes defines a kernel lane, separate from Software Delivery lane definitions under software_delivery.lanes.definitions. Replace this complete array with pnpm config:set --key lanes --json-value '<array>'; dotted lanes.* mutation is refused, and omitted scopes never receive inferred grants. The existing lane:edit two-plane behavior for Software Delivery remains unchanged.

FieldTypeRequiredDescription
idstringYesLane identifier (kebab-case, e.g., framework-core)
titlestringYesHuman-readable name (e.g., Framework: Core)
allowed_scopesToolScope[]NoScopes this lane grants (defaults to [])
wip_limitintegerNoMaximum concurrent active tasks
policy_overridesobjectNoLane-level policy configuration

The lane’s allowed_scopes are used as the second level of the scope intersection algorithm:

effective_scope = workspace ∩ lane ∩ task ∩ tool

The security object defines the workspace-level permission boundary — the outermost layer of the scope intersection. Replace the complete object with pnpm config:set --key security --json-value '<object>'. Dotted security.* writes are refused; config:unset can remove only optional fields that leave the required object valid.

FieldTypeRequiredDescription
allowed_scopesToolScope[]YesMaximum permissions any tool can receive
deny_overlaysstring[]YesGlob patterns for unconditionally denied paths

security.network_default is retired. Specify network posture on each network entry in security.allowed_scopes; do not set a workspace-wide default.

The old memory_namespace and event_namespace root fields are retired and should be removed from existing workspace files. control_plane.policy_mode is temporarily accepted as deprecated workspace input for compatibility, but the parsed runtime configuration ignores it and lumenflow:doctor warns while it is present. Remove it with pnpm config:unset --key control_plane.policy_mode. It remains accepted only for at least one release. A provided value is enum validated and stripped from effective workspace configuration. The programmatic SDK Mock policy mode and Cloud database/wire contracts are unchanged. cloud:connect --policy-mode is likewise retired with no effect: the flag is still accepted and validated for compatibility, prints one warning when passed, and never writes policy_mode. Policy mode is governed by the Cloud workspace policy setting, not by workspace.yaml.

Scopes can be path or network types:

security:
  allowed_scopes:
    # Path scope: grant write access to all files
    - type: path
      pattern: '**'
      access: write

    # Network scope: deny all network access
    - type: network
      posture: 'off'

The network scope posture field accepts three values:

PostureDescription
offAll network access blocked
allowlistOnly traffic to listed hosts/CIDRs is permitted
fullUnrestricted network access

When posture is allowlist, the allowlist_entries field is required:

FieldTypeRequiredDescription
allowlist_entriesstring[]Yes (when posture=allowlist)Array of permitted host:port or CIDR entries
security:
  allowed_scopes:
    - type: network
      posture: allowlist
      allowlist_entries:
        - 'registry.npmjs.org:443' # host:port format
        - 'api.github.com:443' # host:port format
        - '10.0.0.0/24' # CIDR format

The kernel validates that allowlist_entries is present and non-empty when posture is allowlist. Omitting it or passing an empty array produces a Zod validation error at startup.

Network scopes participate in scope intersection across all four layers (workspace, lane, task, tool). See the scope intersection page for the deny-wins semantics and entry-level set intersection behavior.

Paths in deny_overlays are unconditionally denied regardless of scope intersection. The kernel checks these before any other authorization step:

security:
  deny_overlays:
    - '.lumenflow/**' # Kernel state is tamper-proof
    - '.git/**' # Git internals are protected

Commands locate the project by walking up from the current directory and stopping at the first directory that holds workspace.yaml or .git. That walk has fixed boundaries:

  • The enclosing repository wins. A workspace.yaml in a directory above your repository is never adopted: the walk stops at the repository’s .git first.
  • A temp directory root is never a project. The system temp directory (os.tmpdir(), and /tmp or /var/tmp on macOS and Linux) is not treated as a project root, and the walk never climbs past it. A stray workspace.yaml or .git left in the temp directory is ignored, and so is anything above a temp directory (for example, a home-directory dotfiles repository above a custom TMPDIR).
  • An explicit project root is a boundary. When a command or API is given a project root, it reads workspace.yaml at that root only. If the root has none, the built-in defaults apply.

Running a command from a scratch directory under the temp directory, with no project inside it, fails with a “missing workspace.yaml” error instead of silently using another project’s configuration. Run pnpm lumenflow:init there, or run the command from inside your project.

The kernel validates workspace.yaml against WorkspaceSpecSchema (defined in @hellmai/lumenflow-kernel) at startup. Invalid specs cause initializeKernelRuntime() to throw with a Zod validation error.

# The kernel validates automatically during initialization.
# There is no separate public CLI validation command for kernel-only workspace specs.
# Validate by booting the kernel runtime with your workspace.yaml in place.

The software_delivery root key is a pack config block owned by the Software Delivery pack. It exists in workspace.yaml only when the pack is pinned and declares config_key: software_delivery in its manifest.

Concernworkspace.yaml top-level (kernel)workspace.yaml > software_delivery (pack)
AudienceKernel runtimeCLI and tooling layer
LanesLaneSpec (id, scopes, wip)Lane definitions (name, code_paths)
PacksPackPin (id, version, integrity)Not present
SecurityScope intersection boundariesGit hooks, safe-git wrapper
GatesPack policies (on_completion)Gate commands, conditional commands
ValidationZod WorkspaceSpecSchema at initPack config schema via config:set

The software_delivery.gates block configures quality gate commands and conditional commands.

Pattern-triggered commands that run only when matching files change:

software_delivery:
  gates:
    commands:
      test_full: 'pnpm test'
      test_incremental: 'pnpm test --changed'
      typecheck: 'pnpm typecheck'

    conditional_commands:
      - trigger_patterns:
          - 'supabase/migrations/**'
          - 'supabase/schema.sql'
        command: 'pnpm db:verify'
        severity: error
        fresh_on_completion: true
        guidance: 'Apply pending migrations before verifying.'
        guidance_ref: 'docs/db-verification-guide.md'
FieldTypeRequiredDescription
trigger_patternsstring[]YesGlob patterns matched against changed files (min 1)
commandstringYesShell command to execute
severitystringNoerror (default), warn, or off
fresh_on_completionbooleanNoRerun at wu:done after reconciliation; defaults to false
guidancestringNoActionable text shown on failure
guidance_refstringNoFile path whose content is appended to guidance on failure

Fresh completion commands ignore otherwise valid preparation-checkpoint reuse and fail closed if they fail or cannot run. Their completion flow evidence records the WU, reconciled commit and diff, command, completion timestamp, and result. Conditional commands without the flag retain ordinary checkpoint reuse.

See Gates for usage details.

  • Kernel Runtime — How the workspace spec is loaded and used
  • Packs — Domain pack architecture and the Software Delivery Pack
  • Scope Intersection — How workspace security becomes effective permissions
  • Configuration — software_delivery configuration reference