Dynamic values
Dynamic values let you compute context, env, and concurrencyGroup at runtime based on the incoming event. Instead of hardcoding static strings, you pass a function that receives the normalized event envelope and returns the resolved value.
job('deploy', { runsOn: ['default'], context: (event) => event.targetBranch, env: (event) => ({ BRANCH: event.targetBranch }), concurrencyGroup: (event) => `deploy-${event.targetBranch}`, steps: [/* ... */],});job('deploy', { runsOn: 'default', // One shape everywhere: branch on the normalized event type. context: (event) => (event.type === 'pull_request' ? 'preview' : 'production'), steps: [/* ... */],});How it works
Section titled “How it works”When you define a dynamic value as a function, it is resolved on the eval agent as a short init step that runs before the job:
- The orchestrator dispatches a lightweight
__init__job to an agent. - The agent loads the compiled workflow bundle and calls your function with the normalized event.
- The agent reports the resolved values back to the orchestrator, which dispatches the real execution job with them applied.
This resolution appears in the run timeline as an Init: entry. The orchestrator never evaluates workflow code — every dynamic context, env, and concurrencyGroup function runs agent-side, whatever it references.
kici preview lists the injected __init__ job under each affected job, so you can spot it before the first run.
Examples:
// Simple branch extractioncontext: (event) => event.targetBranch;
// Object literal with string operationsenv: (event) => ({ BRANCH: event.targetBranch });
// Concatenation with event dataconcurrencyGroup: (event) => `deploy-${event.targetBranch}`;
// Local variables and safe globalscontext: (event) => { const parts = event.targetBranch.split('/'); return parts[parts.length - 1];};
// Async lookups, module access, and process/global reads all workcontext: async (event) => await lookupEnv(event.targetBranch);env: (event) => ({ DEFAULT_ENV: process.env.DEFAULT_ENV ?? 'staging' });Performance
Section titled “Performance”| Value | Overhead | Example |
|---|---|---|
| Static value | None | context: 'staging' |
| Dynamic value (function) | Init step | context: (event) => event.targetBranch |
A static value is baked into the lock file and needs no init step. A dynamic value always resolves through the agent’s init step, so reach for a function only when the value genuinely depends on the event.
- Prefer static values when you can. Most context and env values are the same on every event; only make them dynamic when they truly depend on the event payload.
- Run
kici previewto see the injected__init__job listed under each affected job before your first run. - A runtime error in a dynamic function fails the job. If your function throws when the init step runs it (e.g., accessing a property on
undefined), the job fails immediately. - See how your workflow code executes for the full picture of where dynamic values run relative to rules, hooks, and step bodies.
- The event parameter is the normalized event envelope — the same shape rules receive as
ctx.event:{ type, action, targetBranch, sourceBranch, changedFiles, payload, … }(see the event payload reference for the complete schema). Narrow onevent.type('push','pull_request','tag', …) to branch per trigger kind. The raw provider webhook body is nested atevent.payload(for GitHub pushes:payload.ref,payload.after,payload.repository, …).