Telo Evaluation Context Model (v1.0 Draft)
1. Overview
The Telo engine relies on the Common Expression Language (CEL) to evaluate dynamic properties within declarative YAML manifests. To evaluate these expressions safely and deterministically, the kernel must provide a strictly defined Evaluation Context.
This context is conceptually divided into two distinct layers: the Module Context (initialized at boot) and the Execution Context (created per trigger/request).
2. Core Principles
- Immutability: The Module Context MUST be strictly read-only after the module initialization phase. The Execution Context is immutable for the duration of a single execution.
- Namespace Isolation: Top-level keys (namespaces) are strictly defined and reserved. A user cannot arbitrarily inject new top-level keys into the context.
- Security by Design: Sensitive data is explicitly isolated in the
secretsnamespace, allowing the kernel to automatically sanitize logs and error traces. - Performance: The kernel SHOULD NOT perform deep copies of the Module Context per execution. It should use shallow merging, references, or language-specific proxy patterns to combine the contexts efficiently.
3. The Context Layers
3.1. Module Context
The Module Context represents the state of the module after it has been loaded, configured, and initialized. It is shared across all incoming executions.
variables(Dictionary/Map): Non-sensitive configuration values provided to the module (e.g., timeouts, public URLs, environment flags).secrets(Dictionary/Map): Sensitive strings or credentials (e.g., API keys, database passwords). Constraint: Any value accessed from this namespace MUST be redacted (e.g., replaced with[REDACTED]) in all standard output, logs, and error messages generated by the kernel.resources(Dictionary/Map): References to instantiated resource objects in the module scope. Includes both locally-defined resources (e.g., an active database connection, an HTTP server instance) and imported modules (keyed by the user-defined alias, mapping to the exported properties of that module instance).
3.2. Execution Context (Runtime Scope)
The Execution Context is highly ephemeral. It is constructed instantly when an event triggers the engine (e.g., an incoming HTTP request, a cron job, a message queue event) and is destroyed upon completion.
request(Dictionary/Map): The payload and metadata of the trigger event. The schema of this object depends on the trigger type (e.g., an HTTP trigger will populatemethod,path,headers,query, andbody).inputs(Dictionary/Map): Local arguments passed directly into a specific handler or action during the execution flow.
4. Context Examples
Example A: Module Context (At Boot Time)
When the Telo kernel starts and initializes a module, it builds this read-only structure in memory.
{
"variables": {
"publicApiUrl": "https://api.telo.run/v1",
"maxRetries": 3,
"environment": "production"
},
"secrets": {
"stripeApiKey": "sk_live_123abc456def...",
"databaseUrl": "postgres://user:pass@db:5432/main"
},
"resources": {
"CacheDb": {
"status": "connected",
"pingMs": 14
},
"AuthModule": {
"sessionGatewayEndpoint": "http://internal-auth:8080"
}
}
}
Example B: Merged Evaluation Context (At Execution Time)
When an event occurs (e.g., a POST request to a route), the kernel injects the Execution Context into the Module Context. This unified object is what the CEL evaluator receives to resolve expressions like ${{ secrets.stripeApiKey }} or ${{ request.body.userId }}.
{
"variables": {
"publicApiUrl": "https://api.telo.run/v1",
"maxRetries": 3,
"environment": "production"
},
"secrets": {
"stripeApiKey": "sk_live_123abc456def...",
"databaseUrl": "postgres://user:pass@db:5432/main"
},
"resources": {
"CacheDb": {
"status": "connected",
"pingMs": 14
},
"AuthModule": {
"sessionGatewayEndpoint": "http://internal-auth:8080"
}
},
"request": {
"method": "POST",
"path": "/users",
"headers": {
"content-type": "application/json",
"user-agent": "curl/7.68.0"
},
"query": {},
"body": {
"userId": "998877",
"action": "upgrade_tier"
}
},
"inputs": {
"targetResource": "billing"
}
}
5. Resource Instantiation Architecture
5.1 Overview
Every EvaluationContext owns its full resource lifecycle. This is achieved by injecting an InstanceFactory function into the context at construction time, rather than passing it per-call to methods like initializeResources().
5.2 InstanceFactory Injection
The InstanceFactory type is a simple async factory function:
type InstanceFactory = (resource: ResourceManifest) => Promise<ResourceInstance | null>;
When the Kernel constructs a ModuleContext, it provides its internal _createInstance method:
// In Kernel constructor
this.moduleContextRegistry = new ModuleContextRegistry(this._createInstance.bind(this));
// In ModuleContextRegistry.declareModule()
this.store.set(moduleName, new ModuleContext({}, {}, {}, this.createInstance));
The _createInstance method handles:
- Resolving alias-prefixed kinds (e.g.,
MyImport.Http.Route→Http.Route) - Looking up the appropriate controller
- Validating the resource against the controller's schema
- Creating and initializing the resource instance
- Storing the instance in both the Kernel registry and the module context
- Invoking post-initialization hooks (e.g.,
isContextProvider)
5.3 Context Hierarchy
The context hierarchy follows a simple pattern:
EvaluationContext(context, createInstance, secretValues)
├── ModuleContext(variables, secrets, resources, createInstance)
│ ├── pendingResources: ResourceManifest[]
│ └── resourceInstances: Map<key, {resource, instance}>
│
└── ExecutionContext(moduleCtx, execProps)
├── inherits createInstance from moduleCtx
└── merges moduleCtx.context with execProps (e.g., {request, inputs})
Key properties:
ModuleContextis stateful and mutable — it accumulates resources during multi-pass initialization as controllers register new kinds.ExecutionContextis ephemeral and read-only — it merges the module context with per-execution properties and forwards the module'screateInstancefactory.EvaluationContextcan spawn child contexts viaspawnChild(), forming a lifecycle tree. All children inherit the parent'screateInstancefactory.
5.4 Multi-Pass Initialization
When initializeResources() is called on any context, it performs a multi-pass loop:
public async initializeResources(): Promise<void> {
const maxPasses = 10;
let pass = 0;
while (pass < maxPasses && this.pendingResources.length > 0) {
pass++;
const toProcess = [...this.pendingResources];
this.pendingResources.length = 0;
for (const resource of toProcess) {
const instance = await this._createInstance(resource);
if (!instance) {
// Dependency not yet ready — queue for next pass
this.pendingResources.push(resource);
} else {
// Resource created and stored by _createInstance
this.resourceInstances.set(resourceKey(resource), { resource, instance });
}
}
}
}
Why multiple passes?
Resources may depend on other resources being created first (e.g., a Kernel.Module resource registers new kinds that other resources need). Rather than requiring explicit topological sorting, the loop retries failed resources until all are resolved or the max passes are exhausted.
5.5 Child Context Initialization
The ResourceContext interface (implemented by ResourceContextImpl) provides methods for controllers that spawn child contexts:
/**
* Create a child EvaluationContext attached to the current module context.
* The child inherits the module's createInstance factory.
*/
spawnChildContext(): EvaluationContext {
const child = new EvaluationContext(
this.moduleContext.context,
this.moduleContext.createInstance, // ← inherited
this.moduleContext.secretValues,
);
return this.moduleContext.spawnChild(child);
}
/**
* Initialize pending resources on a child context.
* Uses the inherited createInstance factory.
*/
async initializeChildContext(ctx: EvaluationContext): Promise<void> {
await this.kernel.initializeContext(ctx); // → ctx.initializeResources()
}
5.6 Design Benefits
- Eliminates
ResourceInstantiatorfrom the public API — the kernel's factory is injected once, not passed per-call. - Simplifies Kernel internals — the
initializationQueueand private 177-line init loop are replaced by delegating toctx.initializeResources(). - Uniform resource ownership — every context type can initialize resources the same way.
- Testability — contexts can be constructed with mock
InstanceFactoryimplementations for unit testing.