Appearance
KAL Core Language Specification Draft
Status: early language draft
Scope: KAL core language semantics
Draft shape: reorganized after scope-boundary review
1. Overview
KAL is a KDL v2-based automation DSL.
Unless otherwise stated, KAL assumes KDL v2 syntax and document rules as the underlying surface format.
KAL is defined as a language and document model. It is not a runtime, product, package manager, inventory system, reporting system, or project-layout specification.
KAL describes automation workflows through:
- explicit workflow components
- scoped data flow
- composable execution units
- operator-based execution semantics
- analyzable component boundaries
- structured guards
- explicit result capture
A concise positioning statement is:
text
A KDL-based automation DSL for scoped, explicit, composable workflow specifications.KAL core defines language-level semantics only. Host-specific reference resolution, toolchain behavior, runtime execution, library operator behavior, extension operator behavior, persistence, reporting, transport, and project layout are outside this specification.
2. Core Design Principles
KAL documents are not arbitrary KDL documents. They follow a stricter component-oriented structure.
At a high level, a KAL document is composed of:
text
component header + operator bodyThe component header declares the component kind and scope interface.
The operator body describes the workflow implementation.
KAL defines three component kinds:
text
plan
recipe
fragmentTheir semantic roles are:
text
plan = top-level executable workflow entrypoint
recipe = plan-facing semantic workflow unit
fragment = splice-only implementation subroutineThe core composition principle is:
text
Higher-level components may invoke lower-level abstractions.
Lower-level components may not invoke higher-level abstractions.In shorter form:
text
Top-down composition is permissive.
Bottom-up composition is restricted.This keeps simple workflows simple while preventing lower-level reusable pieces from secretly orchestrating higher-level workflows.
3. Vocabulary
The DSL is named KAL.
A possible expansion is:
text
KDL Automation LanguageThe core vocabulary is:
text
KAL
plan
recipe
fragment
operator
inputs
locals
outputs
globals
core.use
core.splice
core.each
core.assign
core.load_vars
when
check
captureThe core scope model is summarized as:
text
inputs go down
outputs go up
locals stay local
globals are shared but must be declared4. Document Model
A single KAL component document contains exactly one component header and an operator body.
This rule applies to one component document.
A host or toolchain may organize multiple component documents into a project, package, registry unit, or other larger structure, but that organization is outside KAL core.
The component header is one of:
text
plan
recipe
fragmentA component header declares the component kind and scope interface. It does not declare a component identity.
Component identity, if needed for execution, lookup, display, packaging, or user-facing selection, is host-defined.
The operator body is written after the component header as sibling nodes. Operators are not nested inside a generic body block.
Example:
kdl
recipe {
inputs {
name
}
locals {
normalized_name
}
outputs {
result
}
}
core.assign "Set local value" {
bind inputs.name to=locals.normalized_name
}This preserves a clear distinction:
text
component header = interface and scope declaration
operator body = workflow implementationA generic wrapper body form is intentionally not part of the current draft:
kdl
recipe {
inputs {
name
}
body {
core.assign "Set local value" {
bind inputs.name to=locals.normalized_name
}
}
}Component bodies stay flat by default.
Composite operators may define controlled nested operator bodies, such as core.each.
5. Header Sections
The current component header sections are:
text
inputs
locals
outputs
globalsExample:
kdl
recipe {
inputs {
env
}
locals {
result
}
outputs {
result
}
globals {
shared_value type=string access=read
}
}globals is optional. A component that does not use shared global values does not need a globals section.
5.1 Header Declaration Shape
A header declaration entry uses property-style declaration syntax.
Current canonical forms are:
text
<name> type=<type>?
<name> type=<type>? default=<scalar-or-string-literal>?
<name> type=<type>? access=read|write|readwriteRules:
- declaration names must be bare identifiers
- declaration names must be unique within the same header section
defaultis allowed forinputsandlocals- for
inputsandlocals,defaultmay be a scalar literal or a supported KAL string literal - inline object and array literals are not part of the current declaration-default surface
defaultis not allowed foroutputsaccessis required forglobals- unsupported declaration properties fail validation
6. Scope Model
KAL uses explicit scoped data flow.
The current model is:
text
inputs go down
outputs go up
locals stay local
globals are shared but must be declaredEach component owns its own inputs, locals, and outputs scope.
A child component cannot implicitly read the parent's locals.
A parent component cannot directly read the child's locals.
Normal cross-component data flow must use:
text
caller inputs -> callee inputs
callee outputs -> caller capture destinationglobals provides an explicit shared scope for values that must be visible across component boundaries. It is not an implicit variable bag.
6.1 Inputs
inputs declare values required or accepted by a component.
Rules:
- inputs must be declared before use
- inputs are required by default
- inputs may define scalar or supported KAL string literal default values
- inputs may declare primitive type annotations
- input declarations use the same declaration-property style as other header declarations in the current draft
- input defaults are applied at component invocation time
inputs.*is readable by the owning componentinputs.*is not writable
Canonical examples:
kdl
inputs {
env type=string
retries type=number default=3
}6.2 Locals
locals declare component-local working values.
Rules:
- locals must be declared before general use
- locals may define scalar or supported KAL string literal default values
- locals may declare primitive type annotations
- local declarations use the same declaration-property style as input declarations in the current draft
- local defaults bind during component startup/setup
- locals are not visible to parent or child components unless explicitly passed through inputs or outputs
locals.*is readable and writable by the owning component- reading a local without an assigned value or bound default fails
Canonical examples:
kdl
locals {
normalized_name type=string
retries type=number default=3
}6.3 Outputs
outputs declare public result slots exported by a component.
Rules:
- outputs do not define default values
- outputs may declare primitive type annotations
- outputs may be read and written by the owning component during execution
- reading an output is valid only after that output has been assigned a value in the current component execution
- if an output is read before assignment and that read is statically knowable, validation fails
- otherwise, reading an output before assignment fails when evaluated
- outputs may be written explicitly more than once
- a successfully completed component must provide a final value for every declared output
- if a missing final output is statically knowable, validation fails
- otherwise, component completion fails if any declared output remains unset
- after successful component completion, final output values become immutable and visible to the caller through
capture - outputs become visible to the caller only after the component ends
When authors want to guarantee output presence across conditional branches, they may use an unconditional initial write before guarded writes that may override it.
A failed child component does not expose partial outputs to its caller.
6.4 Globals
globals is an explicit shared scope for values that must be visible across component boundaries.
A component may read or write a global value only if it declares that global key in its component header.
globals declarations are access declarations. They do not define how global values are created, stored, injected, refreshed, persisted, synchronized, or transported.
The concrete source, lifetime, initialization, and runtime behavior of global values are host/runtime responsibilities.
KAL core defines global access permission, not concurrent write conflict resolution.
If a host introduces parallel execution, it must define deterministic global write behavior or reject unsafe concurrent writes.
globals is not an implicit variable bag. Undeclared globals.* references fail validation.
Example:
kdl
recipe {
inputs {
env
}
locals {
result
}
outputs {
result
}
globals {
shared_value type=string access=read
run_marker type=boolean access=write
shared_result type=object access=readwrite
}
}Rules:
- every component that reads or writes a global key must declare that key
- a
globalsdeclaration is an access declaration, not an ownership declaration - read access is required to read a global key
- write access is required to write a global key
readwritegrants both read and write access- duplicate declarations of the same global key across resolved components must use compatible types
- mismatched duplicate global key types fail validation when analyzable
globals.*may be a writable destination only when declared writable- successful writes to
globals.*follow normal execution order globalsshould not replace ordinaryinputs/outputsfor normal component dataflow
Current access values:
text
read
write
readwriteKAL defines the globals access model. It does not define what values a host/runtime provides through globals.
6.5 Type Model
The current core type surface is shallow.
Current declared types are:
text
string
number
boolean
object
array
anyRules:
numberrepresents KAL numeric values without distinguishing integer and float in coreobjectandarrayare structured runtime value categoriesanydisables static type narrowing but does not bypass reference, declaration, or writability validation- unknown type names fail validation
7. Component Model
KAL defines three component kinds:
text
plan
recipe
fragmentAll three component kinds may contain an operator body.
Their difference is semantic role and composition permission, not basic ability to contain operators.
7.1 Plan
A plan is a top-level executable workflow entrypoint.
A plan represents a full workflow that a user intends to run.
A plan may:
- use recipes through
core.use - splice fragments through
core.splice - contain operators directly
A plan must not use another plan.
Example:
kdl
plan {
inputs {
}
locals {
}
outputs {
}
}Allowing operators directly inside a plan keeps simple automation easy. Recipes and fragments are organizational tools; they should improve structure and reuse, but they should not be mandatory for every small workflow.
7.2 Recipe
A recipe is a plan-facing semantic workflow unit.
A recipe represents a reusable workflow operation that should be visible at the plan composition level.
A recipe may:
- contain operators directly
- splice fragments through
core.splice
A recipe must not:
- use another recipe
- use a plan
Recipes are reusable workflow units for plans, but recipes should not secretly orchestrate other recipes. If multiple recipes need to be composed together, that composition should happen at the plan level.
Example:
kdl
recipe {
inputs {
env
}
locals {
result
}
outputs {
result
}
}
core.splice "Run internal phase" {
fragment "prepare-service-phase"
inputs {
env inputs.env
}
capture {
result -> outputs.result
}
}In short:
text
Recipe = plan-facing semantic workflow unit7.3 Fragment
A fragment is a splice-only implementation subroutine.
A fragment can contain meaningful workflow implementation, but it does not have recipe-like public execution identity.
A fragment has its own scoped interface through inputs, locals, outputs, and optional globals access declarations.
A fragment may:
- contain operators directly
- splice other fragments through
core.splice
A fragment must not:
- use recipes
- use plans
- be targeted by
core.use - be executed independently as a formal run target
A fragment may only be invoked through core.splice.
Example:
kdl
fragment {
inputs {
env
}
locals {
result
}
outputs {
result
}
}
core.assign "Expose result" {
bind inputs.env to=outputs.result
}In short:
text
Fragment = internal workflow subroutine8. Composition Rules
KAL separates three component boundaries:
text
plan = top-level executable workflow entrypoint
recipe = plan-facing semantic workflow unit
fragment = splice-only implementation subroutineThe composition table is:
| From \ To | plan | recipe | fragment | operators |
|---|---|---|---|---|
plan | no | core.use | core.splice | yes |
recipe | no | no | core.splice | yes |
fragment | no | no | core.splice | yes |
This means:
- a plan can use recipes, splice fragments, and contain operators directly
- a recipe can splice fragments and contain operators directly, but cannot use recipes
- a fragment can splice fragments and contain operators directly, but cannot use recipes
- all component kinds may contain operators directly
The key principle is:
text
Dependency direction must go downward.
Execution composition may skip layers downward.Allowed examples:
text
Plan -> Recipe -> Fragment -> Operator
Plan -> Fragment -> Operator
Plan -> Operator
Recipe -> Fragment -> Operator
Recipe -> Operator
Fragment -> Fragment -> Operator
Fragment -> OperatorDisallowed examples:
text
Plan -> Plan
Recipe -> Recipe
Recipe -> Plan
Fragment -> Recipe
Fragment -> Plan
Operator -> any componentComposition across core.use and core.splice must remain acyclic.
Any detected composition cycle must fail validation before execution.
9. Host-Resolved References
KAL defines document and data references at the semantic level.
Some core operators reference other KAL components or data documents.
KAL requires that these references resolve to the required semantic target before execution or before any static validation that depends on the target shape.
KAL core does not define concrete reference syntax, lookup mechanisms, filesystem layout, package layout, path aliases, registry lookup, namespace construction, dependency resolution, or shared-resource conventions. Those are host or toolchain responsibilities.
9.1 Reference Resolution
Core references used by core.use, core.splice, and core.load_vars must resolve before execution.
KAL defines the required semantic target of each reference:
core.use.recipemust resolve to exactly onerecipecomponentcore.splice.fragmentmust resolve to exactly onefragmentcomponentcore.load_vars.sourcemust resolve to exactly one valid KDL v2 vars document
The concrete reference syntax and lookup mechanism are host-defined.
If a reference cannot be resolved, resolves ambiguously, or resolves to the wrong target kind, validation must fail.
10. Reference Model
KAL references are explicit and scope-prefixed.
Basic examples:
text
inputs.env
locals.version
outputs.changed
globals.shared_valueRules:
- references must begin with an explicit scope prefix
- deeper path access is supported for structured runtime values
- index access is supported
- bare identifiers are not valid general references
globals.*references require a matchingglobalsaccess declaration in the current componentloop.*references are only valid inside loop-provided scopes such ascore.each
Examples:
text
locals.release.version
locals.items[0].name
outputs.results[0].changed
globals.shared.valueCurrent canonical scalar example forms for KDL v2 in KAL are:
text
#true
#false
#nullBare true, false, and null should not be used as canonical KAL scalar examples.
11. String and Interpolation Model
KAL string handling is a KAL-layer rule rather than a native KDL string feature.
String literals used in operator bodies are interpreted according to the KAL string model.
The current draft accepts these KAL string literal forms in operator bodies:
- ordinary quoted strings
- multi-line strings
- raw strings
- raw multi-line strings
Only ordinary quoted strings and multi-line strings are interpolation-capable.
Raw strings and raw multi-line strings are treated as literal string values and do not perform ${...} interpolation.
The current draft does not define bare identifiers as KAL string literal forms.
Declaration-time strings in component headers are not interpolated.
The only interpolation form is:
text
${reference}Rules:
referencemust be a valid KAL reference- interpolation does not support expressions
- interpolation does not support function calls
- interpolation does not support nested interpolation
\${means a literal${sequence inside an interpolation-capable string${without a matching closing}fails validation${}fails validation${...}whose contents are not a valid KAL reference fails validation
Current interpolation value contract:
- scalar values such as
string,number, andbooleanare valid interpolation inputs - object and array values are not implicitly stringified
- if a non-scalar interpolation value is statically knowable, validation should fail
- otherwise, a non-scalar interpolation value fails when interpolation is attempted
Reference fields used by core document-loading or data-loading semantics must not rely on general interpolation in the current draft.
Interpolation is evaluated before the resulting string value is passed to an operator or bound by core.assign.
Multi-line string values follow the same interpolation model as ordinary quoted string values.
12. Operator Model
Everything executable in KAL is modeled as an operator.
KAL uses a three-way operator classification:
text
Operator
├── Composite Operator
├── Data Operator
└── Action Operator12.1 Composite Operators
Composite operators compose, expand, or control workflow structure.
Core composite operators in the current draft include:
text
core.use
core.splice
core.eachComposite operators do not directly represent target-side actions. Instead, they affect how components are connected, expanded, or controlled.
12.2 Data Operators
Data operators transform, assign, load, or move scoped data.
Core data operators in the current draft include:
text
core.assign
core.load_varsData operators usually operate on controller-side data and interact with KAL scopes.
12.3 Action Operators
Action operators perform external actions against a target, controller, or external system.
KAL core defines the category and common contract shape, but does not define concrete standard-library or platform-specific action operators in this specification.
Concrete non-core operator behavior belongs to host, standard-library, or extension specifications.
13. Operator Contract
Important operator capabilities should be schema-visible rather than left as undocumented per-operator behavior.
The current minimal abstract operator contract is:
text
operator_id
category
placement
capture.sourcesRecommended additional contract field:
text
nested_bodyCurrent placement values:
text
controller
targetRules:
operator_ididentifies the operator kind, for examplecore.assign, not an optional human-readable invocation label such as"Bind values"placementis mandatory because operator execution semantics are incomplete without itexternalis not part of the current placement surfacenested_bodydefaults tofalsewhen omitted- execution-context differences should be modeled as distinct operators rather than a runtime mode switch when those differences affect semantics
KAL defines required abstract operator-schema semantics, not a concrete manifest format, registry format, or serialization format.
13.1 Execution Outcome
Every operator invocation has a language-level execution outcome:
text
status: ok | skipped | failed
message?: stringThe execution outcome is language-level semantics. It is not a user-visible scope and not a runtime storage model.
Semantics:
okmeans the operator invocation completed successfullyskippedmeans the operator invocation was not executed because execution semantics skipped it, for example due towhenfailedmeans the operator invocation did not complete successfullymessageis optional human-readable textmessagedoes not imply a machine-readable error structure- workflow logic must not depend on
messagecontents - universal boolean mirrors such as
ok=true,failed=true, orskipped=trueare not part of the current draft - runtime/internal execution errors map to
status=failed - partial failed results are not workflow-visible through
captureor other language-level dataflow
The execution outcome is distinct from the operator capture domain.
status and message are not normal capture sources.
KAL does not define a universal composite changed aggregation rule.
Composite operators do not automatically expose changed merely because nested operators or iterations expose changed.
If an operator provides a change-like result, it must define that result explicitly as part of its own capture domain.
13.2 Capture Sources
capture.sources is part of the abstract operator contract.
It is not author-facing DSL syntax.
capture.sources is a flat mapping from named capture source to shallow type.
Current shallow types:
text
string
number
boolean
object
array
anyRules:
_is language-level whole-domain shorthand and is not acapture.sourcesfield- universal execution outcome fields such as
statusandmessageare not part ofcapture.sources changedis not universal- if an operator provides
changed, it appears as a normal named source with typeboolean - capture sources may be static-provided or analyzable-derived
- static-provided sources are fixed by the operator contract
- analyzable-derived sources are resolved from analyzable targets such as invoked component outputs or parsed data-document keys
14. Capture Model
capture is a common KAL binding syntax used at operator boundaries.
capture is not a general KDL operator.
It binds values from an operator's capture domain into writable caller destinations.
The universal execution outcome is distinct from the operator's capture domain. capture reads capture-domain source names, not status or message.
Within capture, _ means the whole capture-domain object for that operator invocation.
Named sources should be used when only a specific value is needed.
14.1 Capture Mapping
Current mapping form:
text
source -> destinationRules:
- the left side must be either a named capture source or
_ - the right side must be an explicit writable destination reference
- writable destinations include
locals.*,outputs.*, and declared writableglobals.*, subject to normal scope rules loop.result.*is also a writable capture destination inside acore.eachdobodyinputs.*is not writable- capture does not create a new reference namespace
- uncaptured results are discarded unless operator semantics say otherwise
Example:
kdl
capture {
result -> locals.result
_ -> locals.full_result
}14.2 Local Declaration Shorthand
The current draft allows capture-only local declaration shorthand:
text
local nameRules:
local foointroduces a newlocals.fooslot- after introduction, later references use
locals.foo - if
locals.fooalready exists,local foofails validation - if the destination already exists, use
locals.foo - body-level local introduction is limited to
capturedestinations local foois not a general write syntax- if a local needs header-style
typeordefaultsemantics, it must be declared in the component header
Example:
kdl
capture {
result -> local normalized_name
}14.3 Failure Behavior
Capture writes occur only when the enclosing operator invocation completes with status=ok.
If an operator invocation is skipped or failed:
- its
captureblock performs no destination writes - partial implementation-local values are not exposed
- a failed child component does not expose partial outputs to its caller
15. Guard Model
when is a universal execution guard.
It is a structured guard, not a full arbitrary boolean expression language.
Current direction:
- a single-condition guard may use inline form
- multi-condition guards use block form with
allorany checkis the predicate clause used inside block-form guards- inline
whenand block-formcheckuse the same predicate set
15.1 Inline Form
Inline form:
text
when <left-operand as required by predicate> <predicate> [right-operand as required by predicate]Rules:
- inline
whenbegins with the predicate's required left operand - the left operand must be an explicit KAL reference for
exists,empty,not_empty,is,is_not,contains,>,>=,<, and<= - the left operand for
inmay be a scalar literal, a string literal, or a readable KAL reference - inline
whencontains exactly one predicate clause - operand presence is determined by predicate arity
- inline
whendoes not support composition - inline
whendoes not support nesting - inline
whendoes not support wrapper negation &&,||, and!are not part of the currentwhensurface
Examples:
kdl
when inputs.enabled is #true
when inputs.count >= 1
when inputs.name not_empty15.2 Block Form
A when block contains one root condition group.
A condition group is either all or any.
An all or any group may contain:
checkpredicate clauses- nested
allgroups - nested
anygroups
Block form uses:
text
when
all
any
checkExample:
kdl
when {
all {
check inputs.enabled is #true
any {
check inputs.env is "prod"
check inputs.env is "staging"
}
}
}Group-level not is not part of the current draft.
15.3 Predicate Set
Current predicate set:
text
is
is_not
exists
empty
not_empty
contains
in
>
>=
<
<=Predicate forms:
| Predicate | Form |
|---|---|
exists | <reference> exists |
empty | <reference> empty |
not_empty | <reference> not_empty |
is | <reference> is <value> |
is_not | <reference> is_not <value> |
contains | <container> contains <item> |
in | <item> in <container> |
> / >= / < / <= | <reference> <predicate> <value> |
Rules:
exists,empty, andnot_emptydo not take a value operandis,is_not,contains,in,>,>=,<, and<=require a value operand- a predicate value operand may be a scalar literal, a string literal, or a readable KAL reference
- predicate value operands must not be function calls, general expressions, operator invocations, or inline object/array literals
- forms such as
when inputs.name exists #truefail validation
Direction:
isandis_notare equality predicates==and!=are not part of the currentwhensurfacecontainsis read as<container> contains <item>inis read as<item> in <container>
16. core.use
core.use composes a plan-facing recipe into a plan.
Conceptually:
text
core.use = compose a recipe into a planAllowed caller:
text
planAllowed target:
text
recipecore.use must not target:
text
plan
fragmentcore.use must not be called from:
text
recipe
fragmentExample:
kdl
core.use "Prepare service" {
recipe "prepare-service"
inputs {
env inputs.env
}
capture {
result -> locals.prepare_result
}
}16.1 Reference Semantics
The recipe field contains a host-resolved recipe reference.
KAL defines only the semantic requirement:
text
the reference must resolve to exactly one recipe componentThe concrete reference form and resolution mechanism are host-defined.
If the recipe reference cannot be resolved, resolves ambiguously, or resolves to a non-recipe component, validation must fail.
16.2 Input Binding
core.use passes values into the recipe through an explicit inputs block.
Recipe inputs are not implicitly read from the caller.
The caller must bind required recipe inputs unless defaults exist.
Bindings for undeclared recipe input keys fail validation.
Extra caller-provided inputs are not allowed in the current draft.
16.3 Capture Behavior
Recipe outputs are not merged implicitly into caller scope.
Recipe output names become core.use capture sources.
Caller-side result handling uses the common capture model.
Uncaptured recipe outputs are discarded.
16.4 Composition Rule
A plan is the only layer that composes recipes.
This ensures that high-level workflow structure remains visible at the plan level.
17. core.splice
core.splice invokes a fragment inside the current component.
Conceptually:
text
core.splice = invoke a fragment subroutineAllowed callers:
text
plan
recipe
fragmentAllowed target:
text
fragmentcore.splice must not target:
text
plan
recipeExample:
kdl
core.splice "Run internal phase" {
fragment "prepare-service-phase"
inputs {
env inputs.env
}
capture {
result -> locals.phase_result
_ -> locals.full_phase_result
}
}17.1 Reference Semantics
The fragment field contains a host-resolved fragment reference.
KAL defines only the semantic requirement:
text
the reference must resolve to exactly one fragment componentThe concrete reference form and resolution mechanism are host-defined.
If the fragment reference cannot be resolved, resolves ambiguously, or resolves to a non-fragment component, validation must fail.
17.2 Input Binding
core.splice passes values into the fragment through an explicit inputs block.
Fragment inputs are not implicitly read from the caller.
The caller must bind required fragment inputs unless defaults exist.
Bindings for undeclared fragment input keys fail validation.
Extra caller-provided inputs are not allowed in the current draft.
17.3 Capture Behavior
Fragment outputs are not merged implicitly into caller scope.
Fragment output names become core.splice capture sources.
Caller-side result handling uses the common capture model.
Uncaptured fragment outputs are discarded.
Within core.splice, _ means the callee's whole declared outputs object.
If the invoked fragment fails, caller-side splice capture performs no writes.
Partial callee outputs are not captured.
17.4 Splice Invocation Semantics
core.splice invokes a fragment component. It does not paste, inline, or textually include the fragment body into the caller.
The invoked fragment keeps its own component scope. Values enter the fragment through inputs, and declared fragment outputs become available to the caller only through explicit capture.
The caller controls where the splice invocation appears in the workflow and how captured outputs are used.
core.splice represents one fragment invocation. It does not define loop aggregation or multi-invocation result collection. If a fragment is invoked repeatedly by an enclosing composite operator such as core.each, aggregation is defined by that enclosing operator.
18. core.each
core.each is the current core iteration composite.
It evaluates an input collection sequentially and executes a nested operator body once for each item.
The in operand must evaluate to an array value.
Objects and strings are not iterable by core.each in the current draft.
Object iteration is not part of the current draft.
18.1 Iteration Semantics
Iterations execute sequentially in input iteration order.
The as clause defines the current item name under the loop.* scope.
Example:
kdl
core.each "Process items" {
in inputs.items
as item
do {
core.assign "Record item" {
bind loop.item to=loop.result.value
}
}
capture {
results -> locals.item_results
}
}In this example, loop.item is the current item for each iteration.
18.2 Loop Scope
Inside the do body, core.each provides a loop.* scope.
The current draft defines:
text
loop.<as-name>
loop.result.*
loop.meta.indexloop.<as-name> is the current item.
loop.meta.index is the zero-based index of the current iteration.
loop.result.* is a reserved writable result object for the current iteration.
Fields under loop.result.* are created by assignment and do not require prior declaration.
Nested operators inside the do body may write to loop.result.* either through capture destinations or through direct writable destinations such as core.assign.
locals.* remains the enclosing component's local scope. It is not automatically materialized into loop results.
18.3 Results
core.each exposes one guaranteed capture source:
text
resultsresults is an ordered array value.
Each results entry corresponds to one iteration in input order.
Each entry is the materialized value of that iteration's loop.result object.
If an iteration does not write any loop.result.* fields, its result entry is an empty object.
results does not automatically include diagnostics, execution traces, component locals, or unrelated working values.
18.4 Failure Semantics
results is exposed only when the whole core.each invocation completes successfully.
If an iteration fails, the core.each invocation fails. Its capture block performs no writes, and partial results from earlier successful iterations are not exposed through capture.
18.5 Nested Body
core.each is the only controlled nested operator-body form defined in the current draft.
The do block contains the nested operator body executed for each iteration.
19. core.assign
core.assign is a core data operator for explicit scoped value binding.
It uses bind entries.
Each bind entry has the form:
text
bind <source> to=<destination>core.assign does not evaluate general expressions.
19.1 Source Values
The source side may be:
- a scalar literal
- a string literal, interpreted according to the KAL string model
- a readable KAL reference
The source side must not be:
- a general expression
- a function call
- an operator invocation
- an undeclared reference
19.2 Destinations
The destination side must be an explicit writable destination reference.
Writable destinations include:
locals.*outputs.*globals.*, when declared writableloop.result.*, inside acore.eachdobody
inputs.* is not writable.
loop.<as-name> and loop.meta.* are not writable.
19.3 Evaluation and Commit
bind entries are evaluated sequentially in document order.
Bindings are evaluated against a staged write set for that core.assign invocation.
Later bind entries may read staged values produced by earlier bind entries in the same core.assign invocation.
If every binding succeeds, all staged writes commit together atomically.
If any binding fails, all staged writes from that core.assign invocation are discarded.
If the same destination is assigned more than once, the last successful binding in document order determines the committed value.
19.4 Type Validation
If a destination declares a type and the assigned value is statically known to be incompatible, validation fails.
If compatibility depends on a runtime value, assignment fails when evaluated.
19.5 Capture
core.assign does not define standard capture sources in the current draft.
It still participates in normal operator execution semantics, including when guards and language-level execution outcome.
Example:
kdl
core.assign "Bind values" {
bind inputs.name to=locals.name
bind #true to=locals.enabled
bind "service-${inputs.name}" to=locals.label
bind locals.name to=outputs.result
}Quoted strings, multi-line strings, raw strings, and raw multi-line strings may be used as string literal source values when supported by the KAL string model.
20. core.load_vars
core.load_vars loads a host-resolved vars document into the caller component through explicit capture.
A vars document is a valid KDL v2 document whose concrete format is host-defined.
The term vars here refers to a literal data document convention, not to a distinct KAL variable scope.
KAL defines the semantic requirements only:
- the resolved document must parse as valid KDL v2
- the resolved document must represent literal data only
- the resolved document must not contain KAL components, operators, guards, capture blocks, or workflow-control constructs
- the resolved document must be mapped by the host into an object-shaped capture domain
- analyzable top-level keys from that host-mapped object-shaped capture domain become named capture sources
The source field contains a host-resolved data-document reference.
KAL defines only the semantic requirement:
text
the reference must resolve to exactly one valid KDL v2 vars documentThe concrete reference form and resolution mechanism are host-defined.
KDL v2 does not require KAL to use one universal top-level node convention for vars documents in this specification.
The concrete document shape, root-node convention, and host-side mapping from KDL v2 surface syntax to that object-shaped capture domain are host-defined.
Rules:
- values in vars documents are literal-only
- workflow logic is not allowed in vars documents
- loaded keys should be analyzable from the host-mapped capture domain
- values are never imported implicitly into caller scope
- named capture sources correspond to top-level keys in the host-mapped capture domain
_captures the whole host-mapped capture domain object- capture destinations follow the normal writable-destination rules
- type validation applies to declared destination types when present
- missing captured keys fail validation when analyzable
- otherwise, missing captured keys fail when evaluated
- successful capture writes follow normal component body execution order
Validation of key existence for named captures is defined against the host-mapped capture domain, not directly against raw KDL surface structure.
Example:
kdl
core.load_vars "Load environment vars" {
source "environment-vars"
capture {
api_url -> locals.api_url
retries -> locals.retries
}
}21. Core Execution Order
By default, entries in a component body execute sequentially in document order.
A component body does not imply parallel execution, deferred execution, or automatic reordering unless a core composite operator explicitly defines such behavior.
A composite operator may define explicit nested execution semantics for its own body.
Later operators may depend on values captured or written by earlier operators.
Conditional follow-up behavior can be expressed by capturing prior results and guarding later operators with when.
Successful writes to writable scopes follow normal execution order.
22. Validation Model
KAL validation should distinguish parsing, static analysis, and runtime-value checks.
Parsing handles malformed surface syntax.
Static analysis is the primary validation stage for KAL semantics that can be determined before execution.
KAL validation is expected to cover at least:
- exactly one component header per KAL component document
- missing required final output values where analyzable
- component header shape
- allowed header sections
- declared scope references
- input, local, output, and global read/write rules
- invalid references to undeclared scope slots
- invalid
globals.*access without matching declaration - invalid
globals.*access mode - mismatched global key types where analyzable
- composition rule violations
- composition cycles
- invalid
core.usecaller or resolved target kind - invalid
core.splicecaller or resolved target kind - unresolved or ambiguous core references before execution
- missing required inputs where analyzable
- extra inputs passed to a component where analyzable
- capture-source existence where analyzable
- capture destination writability
- capture destination declaration rules
- analyzable type mismatches
- malformed
whenpredicates - invalid interpolation syntax
- invalid interpolation reference syntax
- invalid
core.eachloop binding or loop-scope usage - invalid
core.assignbind source or destination shape - invalid
core.load_varssource document or source shape where analyzable
Runtime-value checks should be limited to conditions that depend on values not fully knowable before execution.
Runtime failure is not the intended normal validation stage for analyzable core.use, core.splice, or core.load_vars mismatches.
22.1 Validation Edge Cases
Important current edge-case rules include:
- reading an unassigned
locals.*oroutputs.*value fails validation when statically knowable, otherwise fails when evaluated - successful component completion with any declared output still unset fails validation when statically knowable, otherwise component completion fails
- duplicate destination writes in a single
core.assigninvocation are allowed; the last successful binding in document order determines the committed value - capture to an undeclared local fails validation unless it uses the explicit
local <name>shorthand - predicate arity mismatches such as
when inputs.name exists #truefail validation - input binding keys must match declared component inputs
- extra inputs passed to a component are not allowed in the current draft
This keeps component interfaces explicit. The current draft does not define an open-ended input-object mode for core.use or core.splice.
23. Structured Runtime Values and Inline Literals
KAL permits structured runtime values, including array-like and object-like values, as semantic values.
References may use deeper path access and index access against structured values where such values are produced by inputs, captures, operators, loops, loaded data, or globals.
The current draft does not define a canonical inline surface-literal syntax for authoring array or object values directly in KAL documents.
Spec examples should therefore avoid presenting inline array or object literals as canonical KAL syntax.
Examples should prefer:
- scalar literals
- explicit references
- capture mappings
- operator and component body structures whose KDL v2 reading is clear
Structured runtime values remain valid in KAL semantics even though direct inline literal authoring is intentionally non-canonical in the current draft.
24. Current Core Model Summary
KAL component document:
text
KAL Component Document
= Component Header
+ Operator BodyComponent header:
text
plan | recipe | fragmentHeader sections:
text
inputs
locals
outputs
globalsScope model:
text
inputs go down
outputs go up
locals stay local
globals are shared but must be declaredOperator body:
text
Composite Operators
Data Operators
Action OperatorsCore composition:
text
core.use
compose a recipe into a plan
caller: plan
target: recipe
core.splice
invoke a fragment subroutine
caller: plan / recipe / fragment
target: fragmentCore data movement:
text
core.assign
bind source values to writable scoped destinations
core.load_vars
load a host-defined KDL v2 vars document through explicit capture
capture
bind operator capture-domain values to writable scope destinationsCore control:
text
core.each
sequential iteration with ordered results
when
structured execution guard25. Open Questions
No major unresolved language-shape questions are currently tracked in this core KAL draft.
Remaining refinements are expected to clarify declaration syntax, validation edge cases, canonical examples, and host/spec boundaries without changing the current intended semantics.
Host, toolchain, package, runtime, inventory, secrets, reporting, standard-library, and extension topics should be tracked in their respective specifications.