Skip to content

loopstacks/spec

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 

Repository files navigation

LSEM — Loop Stack Execution Model

Version 0.1 (Draft)

A specification for typed agent loop invocation, composition, lifecycle, and tracing.


Status of this document

This is a working draft. The model is stable enough to implement (see LSR for the reference TypeScript runtime) but the specification text is being refined. Breaking changes between draft versions are possible and will be noted in CHANGELOG.md.

License: Apache 2.0.


1. Motivation

Agentic AI systems are built today by composing model calls, tool invocations, retrieval steps, and routing decisions in framework-specific code. This works for prototypes. It does not produce systems that are easy to trace, easy to compose, easy to swap models within, or easy to evolve over years.

LSEM identifies a single primitive — the typed loop — that, when treated as first-class, makes the long-lived properties of agentic systems easier to achieve. Tracing, composition, schema enforcement, and policy routing become runtime concerns rather than glue-code concerns.

LSEM is intentionally small. It defines the primitive and its lifecycle. It does not define a coordination protocol, a deployment model, a security model, or a UI. Those concerns belong to layers above.


2. Terminology

The key words MUST, MUST NOT, REQUIRED, SHALL, SHALL NOT, SHOULD, SHOULD NOT, RECOMMENDED, MAY, and OPTIONAL in this document are to be interpreted as described in RFC 2119.

  • Loop — a single typed unit of execution. Defined by a LoopDef. Invoked as a LoopCall. Produces a LoopResult.
  • Loop Definition (LoopDef) — the static description of a loop: its identity, its input/output schemas, and its execution body.
  • Loop Call (LoopCall) — a single runtime invocation of a loop with a specific input.
  • Loop Result (LoopResult) — the outcome of a loop call: status, output (if successful), error (if failed), and the full trace.
  • Trace Event (TraceEvent) — a single observable event in the lifecycle of a loop call.
  • Backend — an external system invoked by a loop. Most commonly a language model (OpenAI, Anthropic, etc.), but also tools, databases, or any other side-effecting resource.
  • Registry — an indexed collection of LoopDef records. Loops are referenced by id.
  • Executor — the component responsible for taking a LoopDef plus an input and producing a LoopResult while emitting TraceEvents.

3. Loop Kinds

A loop MUST be exactly one of three kinds:

  • prompt — the loop body is a prompt template invoked against a backend (typically an LLM).
  • composite — the loop body is a sequence of calls to other loops.
  • tool — the loop body is an opaque function (typically a side-effecting operation: a database query, an HTTP call, a calculation).

All three kinds share the same input/output schema model, the same lifecycle, and the same trace event vocabulary. They differ only in execution semantics.


4. The LoopDef Schema

A LoopDef is a JSON document. The following fields are defined:

interface LoopDef {
  id: string;                    // REQUIRED. Unique within a registry.
  name: string;                  // REQUIRED. Human-readable.
  description?: string;          // OPTIONAL.
  version: string;               // REQUIRED. Semver.

  inputSchema: JSONSchema;       // REQUIRED. JSON Schema for input.
  outputSchema: JSONSchema;      // REQUIRED. JSON Schema for output.

  kind: "prompt" | "composite" | "tool";  // REQUIRED.

  prompt?: PromptSpec;           // REQUIRED if kind == "prompt".
  composite?: CompositeSpec;     // REQUIRED if kind == "composite".
  tool?: ToolSpec;               // REQUIRED if kind == "tool".

  backend?: BackendRef;          // OPTIONAL. Default backend for prompt loops.
  policy?: PolicySpec;           // OPTIONAL. Routing/safety policy.

  metadata?: Record<string, unknown>;  // OPTIONAL.
}

A loop MUST declare an inputSchema and outputSchema. These are JSON Schema documents (Draft 2020-12 RECOMMENDED). The runtime MUST validate every input against inputSchema before execution begins, and every output against outputSchema before returning a successful result.


5. The Loop Lifecycle

Every loop call proceeds through a defined lifecycle. The executor MUST emit a TraceEvent for each transition. The lifecycle is:

  call.started
       │
       ▼
  call.input.validated  ─── (or call.errored if invalid)
       │
       ▼
  [kind-specific body]
       │
       ▼
  call.output.validated  ─── (or call.errored if invalid)
       │
       ▼
  call.completed

For kind: "prompt", the kind-specific body is:

  call.backend.requested
       │
       ▼
  call.backend.responded  ─── (or call.errored on backend failure)

For kind: "composite", the kind-specific body is a sequence of nested calls. Each nested call emits its own complete lifecycle, framed by child.started / child.completed events on the parent's trace.

For kind: "tool", the kind-specific body is:

  call.tool.invoked
       │
       ▼
  call.tool.returned  ─── (or call.errored on tool failure)

6. Trace Events

A TraceEvent is the unit of observability. Every event MUST carry:

interface TraceEvent {
  callId: string;       // REQUIRED. The call this event belongs to.
  ts: string;           // REQUIRED. ISO 8601 timestamp.
  type: TraceEventType; // REQUIRED. Defined event type.
  payload?: unknown;    // OPTIONAL. Type-specific structured data.
}

The defined event types in v0.1 are:

Event type Emitted when
call.started A loop call begins. Payload: { loopId, loopVersion, parentCallId? }
call.input.validated Input passes inputSchema validation. Payload: { durationMs }
call.backend.requested A prompt-kind loop sends a request to its backend. Payload: { backendId, model, promptLength }
call.backend.responded The backend returns a response. Payload: { durationMs, inputTokens, outputTokens }
call.tool.invoked A tool-kind loop invokes its function. Payload: { toolName }
call.tool.returned The tool function returns. Payload: { durationMs }
child.started A composite-kind loop begins a nested call. Payload: { childCallId, loopId, stepIndex }
child.completed A nested call completes. Payload: { childCallId, status }
call.output.validated Output passes outputSchema validation. Payload: { durationMs }
call.completed The call completes successfully. Payload: { totalDurationMs }
call.errored The call terminates with an error. Payload: { code, message, details? }
log A free-form log line emitted by the loop body. Payload: { level, message }

Implementations MAY define additional event types in their own namespace (e.g., myorg.cache.hit). They MUST NOT redefine the meaning of the event types listed above.


7. Composition Semantics

Composite loops are the mechanism by which all multi-step patterns — RAG, tool-calling, multi-agent — are expressed in LSEM. There is one composition primitive, not a separate primitive for each pattern.

A CompositeSpec declares a sequence of steps. Each step references another loop by id and provides an inputMapping that constructs the step's input from prior context (the outer input and any prior step outputs).

interface CompositeSpec {
  steps: CompositeStep[];
}

interface CompositeStep {
  loopId: string;
  inputMapping: Record<string, JSONPath>;
  outputBinding?: string;
}

The executor MUST execute steps in order. Each step's LoopResult becomes available to subsequent steps via the binding name. The composite loop's output is constructed from the final step's output (or via an explicit outputMapping — to be specified in v0.2).


8. Policy and Routing (Sketch — v0.2)

LSEM v0.2 will define a PolicySpec for runtime routing decisions: which backend to invoke, fallback behavior on failure, rate limiting, and content filtering hooks. v0.1 leaves this implementation-defined; LSR provides a basic policy mechanism that is not yet promoted to the spec.


9. Conformance

A runtime is LSEM v0.1 conformant if it:

  1. Accepts LoopDef documents that conform to the schema in §4
  2. Validates input and output against the declared schemas
  3. Implements the lifecycle described in §5 for all three loop kinds
  4. Emits the trace events described in §6 with the specified payloads
  5. Implements composite execution semantics as described in §7

A conformance test suite is forthcoming.


10. Non-Goals

LSEM does not specify:

  • A wire protocol for distributing loops between processes or hosts
  • An authentication or authorization model
  • A secret management model
  • A deployment topology (single-process, microservices, Kubernetes, serverless are all valid)
  • A coordination protocol for multiple agents bidding on shared work
  • A user interface

These belong to layers above LSEM. The reference runtime (LSR) makes choices about each, but those choices are not part of the specification.


11. Acknowledgements

LSEM grew out of several years of work on agent coordination patterns and the recurring observation that the primitive — what an agent invocation actually is — was being assumed rather than specified. The model presented here is the result of repeatedly asking "what is the smallest thing that, if it existed, would let everything else be built cleanly on top?"


12. Changelog

  • v0.1 (Draft) — Initial public draft. Defines the three loop kinds, the lifecycle, and the trace event vocabulary.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors