inputVariables.settingsholds ALL immutable task fields — client-provided + server-enriched at creationcontext(replacesworkflowContext) holds only mutable Task-level runtime state written by the aggregator- Task protobuf remains the storage/API layer — fields copied from
settingsat creation - Go code reads
vm.task.*directly — never fromcontextorsettingsmaps - No storage backfill — old workflows must be re-created by users
| Field | Set by | Description |
|---|---|---|
settings.name |
Client (REQUIRED) | Workflow name |
settings.runner |
Client (REQUIRED) | Smart wallet address |
settings.chain |
Client (recommended) | Chain name (e.g. "sepolia") — not yet enforced by server |
settings.chain_id |
Client (recommended) | Chain ID (e.g. 11155111) — not yet enforced by server |
settings.isSimulation |
Client (optional) | Simulation flag |
settings.tokens |
Client (recommended) | Array of token contract addresses used by this workflow (see §7) |
settings.uniswapv3_pool |
Client (optional) | Domain-specific config |
settings.uniswapv3_contracts |
Client (optional) | Domain-specific config |
settings.id |
Aggregator (on create) | Task ID |
settings.owner |
Aggregator (on create) | Owner EOA address |
settings.startAt |
Aggregator (from CreateTaskReq.start_at) | Task start time (ms) |
settings.expiredAt |
Aggregator (from CreateTaskReq.expired_at) | Task expiry time (ms) |
settings.maxExecution |
Aggregator (from CreateTaskReq.max_execution) | Max execution limit |
| Field | Persisted as | When written |
|---|---|---|
context.status |
task.Status |
On state transitions |
context.executionCount |
task.ExecutionCount |
Incremented before each execution |
context.lastRanAt |
task.LastRanAt |
Updated before each execution |
context.completedAt |
task.CompletedAt |
Set when task completes/fails |
Note: executionIndex is NOT a Task-level field. It belongs on the Execution message (Execution.index, protobuf field 6) — it's a per-execution sequential counter assigned by AssignNextExecutionIndex() and stored on the Execution object. It was previously stashed in workflowContext only as a plumbing hack for email subject formatting ("Run #X:") in vm_runner_rest.go. The proper fix is to pass it through the VM as a dedicated execution-scoped field (e.g. vm.ExecutionIndex), not as part of context.
| CreateTaskReq field | Current behavior | New behavior | SDK action |
|---|---|---|---|
name (field 6) |
Client sends, stored as task.Name |
Ignored. Aggregator reads from input_variables.settings.name |
Stop sending. Put in input_variables.settings.name instead |
smart_wallet_address (field 3) |
Client sends, stored as task.SmartWalletAddress |
Ignored. Aggregator reads from input_variables.settings.runner |
Stop sending. Put in input_variables.settings.runner instead |
start_at (field 4) |
Client sends | No change. Aggregator also copies into settings.startAt |
Keep sending as top-level field |
expired_at (field 5) |
Client sends | No change. Aggregator also copies into settings.expiredAt |
Keep sending as top-level field |
max_execution (field 8) |
Client sends | No change. Aggregator also copies into settings.maxExecution |
Keep sending as top-level field |
trigger (field 12) |
Client sends | No change | No change |
nodes (field 13) |
Client sends | No change | No change |
edges (field 14) |
Client sends | No change | No change |
input_variables (field 15) |
Client sends (optional) | MUST include settings key with name, runner (required); chain, chain_id (recommended) |
Ensure settings is always populated |
GetTask returns the full Task protobuf via task.ToProtoBuf().
| GetTask response field | Current behavior | New behavior | SDK action |
|---|---|---|---|
id (field 1) |
Returned | No change | No change |
owner (field 2) |
Returned | No change | No change |
smart_wallet_address (field 3) |
Returned | No change (populated from settings.runner on create) |
No change |
start_at (field 4) |
Returned | No change | No change |
expired_at (field 5) |
Returned | No change | No change |
name (field 6) |
Returned | No change (populated from settings.name on create) |
No change |
completed_at (field 7) |
Returned | No change | No change |
max_execution (field 8) |
Returned | No change | No change |
execution_count (field 9) |
Returned | No change | No change |
last_ran_at (field 10) |
Returned | No change | No change |
status (field 11) |
Returned | No change | No change |
trigger (field 12) |
Returned | No change | No change |
nodes (field 13) |
Returned | No change | No change |
edges (field 14) |
Returned | No change | No change |
input_variables (field 15) |
Returned | Now contains enriched settings with server fields (id, owner, startAt, expiredAt, maxExecution) |
SDK can read settings from input_variables if needed |
Summary: GetTask response is fully backward-compatible. The only observable change is that input_variables.settings now contains additional server-enriched fields.
ListTasks returns a filtered Task with fields selectively populated (engine.go:2116-2139).
| ListTasks response field | Current behavior | New behavior | SDK action |
|---|---|---|---|
id (field 1) |
Always included | No change | No change |
owner (field 2) |
Always included | No change | No change |
smart_wallet_address (field 3) |
Always included | No change | No change |
start_at (field 4) |
Always included | No change | No change |
expired_at (field 5) |
Always included | No change | No change |
name (field 6) |
Always included | No change | No change |
completed_at (field 7) |
Always included | No change | No change |
max_execution (field 8) |
Always included | No change | No change |
execution_count (field 9) |
Always included | No change | No change |
last_ran_at (field 10) |
Always included | No change | No change |
status (field 11) |
Always included | No change | No change |
trigger (field 12) |
Always included | No change | No change |
nodes (field 13) |
Only if IncludeNodes flag set |
No change | No change |
edges (field 14) |
Only if IncludeEdges flag set |
No change | No change |
input_variables (field 15) |
Not included in ListTasks | No change (still not included) | No change |
Summary: ListTasks response is fully backward-compatible. No changes.
| Aspect | Current behavior | New behavior | SDK action |
|---|---|---|---|
input_variables.settings |
Optional — aggregator fell back to taskName := "Workflow" if missing |
REQUIRED — same as CreateTask. Aggregator validates settings.name and settings.runner via shared ValidateInputVariablesSettings(). Rejects with InvalidArgument if missing. |
SDK must always pass inputVariables.settings with name and runner |
input_variables.settings.name |
Ignored (hardcoded to "Workflow") |
REQUIRED. Used as task.Name on the simulation task object |
Pass workflow name in settings.name |
input_variables.settings.runner |
Ignored (not set on simulation task) | REQUIRED. Used as task.SmartWalletAddress on the simulation task object |
Pass smart wallet address in settings.runner |
| Validation | No validation — any or no inputVariables accepted | Dual-layer validation: RPC layer (rpc_server.go) validates protobuf, engine layer (engine.go) validates Go native types |
No SDK change needed (server rejects bad requests) |
workflowContext |
Not available in simulation | Replaced by context (also not available in simulation) |
No change |
| Aspect | Current behavior | New behavior | SDK action |
|---|---|---|---|
input_variables.settings |
Client sends runner, chain, chain_id |
Same — no change | No change (already uses settings for runner context) |
| Old variable | New variable | Migration |
|---|---|---|
{{workflowContext.name}} |
{{settings.name}} |
User must update custom code nodes |
{{workflowContext.owner}} |
{{settings.owner}} |
User must update custom code nodes |
{{workflowContext.runner}} |
{{settings.runner}} |
User must update custom code nodes |
{{workflowContext.smartWalletAddress}} |
{{settings.runner}} |
User must update custom code nodes |
{{workflowContext.eoaAddress}} |
{{settings.owner}} |
User must update custom code nodes |
{{workflowContext.id}} |
{{settings.id}} |
User must update custom code nodes |
{{workflowContext.startAt}} |
{{settings.startAt}} |
User must update custom code nodes |
{{workflowContext.expiredAt}} |
{{settings.expiredAt}} |
User must update custom code nodes |
{{workflowContext.maxExecution}} |
{{settings.maxExecution}} |
User must update custom code nodes |
{{workflowContext.chain}} |
{{settings.chain}} |
User must update custom code nodes |
{{workflowContext.status}} |
{{context.status}} |
User must update custom code nodes |
{{workflowContext.executionCount}} |
{{context.executionCount}} |
User must update custom code nodes |
{{workflowContext.lastRanAt}} |
{{context.lastRanAt}} |
User must update custom code nodes |
{{workflowContext.completedAt}} |
{{context.completedAt}} |
User must update custom code nodes |
Client (Next.js / SDK)
└─ CreateTaskReq
├─ input_variables.settings.name ← REQUIRED
├─ input_variables.settings.runner ← REQUIRED
├─ input_variables.settings.chain ← recommended (not yet enforced)
├─ input_variables.settings.chain_id ← recommended (not yet enforced)
├─ input_variables.settings.* ← optional client fields
├─ start_at, expired_at, max_execution ← top-level scheduling fields
├─ trigger, nodes, edges ← workflow definition
└─ name, smart_wallet_address ← NO LONGER SENT
Aggregator (on CreateTask)
├─ Validates: settings.name, settings.runner MUST be present
├─ Enriches settings with server fields:
│ settings.id = generated task ID
│ settings.owner = authenticated user EOA
│ settings.startAt = CreateTaskReq.start_at
│ settings.expiredAt = CreateTaskReq.expired_at
│ settings.maxExecution = CreateTaskReq.max_execution
├─ Copies to Task protobuf for storage/API backward compat:
│ task.Name = settings.name
│ task.SmartWalletAddress = settings.runner
│ task.Id, task.Owner, etc. = as before
└─ Stores enriched input_variables (settings is now complete)
VM (at execution time)
├─ settings ← loaded from task.input_variables (complete immutable snapshot)
├─ context ← built from mutable Task fields at VM init:
│ context.status = task.Status
│ context.executionCount = task.ExecutionCount
│ context.lastRanAt = task.LastRanAt
│ context.completedAt = task.CompletedAt
└─ Go code reads vm.task.* for internal business logic
File: model/task.go
ValidateInputVariablesSettings()— shared validation for both CreateTask and SimulateTask: requiressettings.name(non-empty) andsettings.runner(valid hex address)NewTaskFromProtobuf()— validates, enriches settings withid,owner,startAt,expiredAt,maxExecution, copies to Task fieldsenrichSettings()— writes server fields back intoinputVariables.settingsbody.Nameandbody.SmartWalletAddressare ignored — all reads come fromsettings
File: aggregator/rpc_server.go (SimulateTask)
- Calls
model.ValidateInputVariablesSettings(req.InputVariables)before processing — same validation as CreateTask - Rejects with
InvalidArgumentifsettings.nameorsettings.runneris missing
File: core/taskengine/engine.go (SimulateTask)
- Engine-level validation of
inputVariables["settings"]withnameandrunner(for Go unit tests that call engine directly) - Sets
task.Nameandtask.SmartWalletAddressfrom settings on the simulation task object - No more
"Workflow"fallback name
- Refactor summarizers, runners, executor to read
vm.task.*instead ofworkflowContextorsettingsmaps - Remove
workflowContextreads from all Go files chain/chain_idcontinue to be read fromsettingsviavm.vars["settings"]- Pass
executionIndexthroughvm.ExecutionIndexfield instead of stashing in workflowContext map
File: vm.go:393-408
// BEFORE (13 fields, mostly duplicating settings):
workflowContext := map[string]interface{}{
"id": task.Id, "name": task.Name, "owner": task.Owner, ...
}
v.AddVar("workflowContext", workflowContext)
// AFTER (4 fields, mutable Task-level runtime state only):
context := map[string]interface{}{
"status": getTaskStatusString(task.Status),
"executionCount": task.ExecutionCount,
"lastRanAt": task.LastRanAt,
"completedAt": task.CompletedAt,
}
v.AddVar("context", context)settings is already loaded from task.InputVariables at vm.go:413-418.
SDK (ava-sdk-js):
Workflow.toRequest(): passesinputVariablestransparently (callers providesettingswithnameandrunner)toRequest()no longer callssetName()orsetSmartWalletAddress()on CreateTaskReqsimulateWorkflow(): all calls now passinputVariables.settingswithnameandrunner- Test helpers:
getSettings(runner, name)returns{ name, runner, chain_id, chain };getInputVariables(name, runner)wraps in{ settings: ... } - All tests updated to use
inputVariables.settingsas the primary way to set workflow metadata
Next.js Studio:
- WorkflowSettings node: already sends settings — no change needed
- Stop sending
body.Name/body.SmartWalletAddressin create workflow calls - Update
variableReferenceUtils.ts: replaceworkflowContextwithcontextandsettings - Update
ExecutionModal.tsx: usecontextfor runtime state
Old tasks without enriched settings will not be migrated. Users re-create their workflows on the client-side.
| Category | JS Variable | Contains | Mutable? |
|---|---|---|---|
| Immutable definition | settings |
id, name, owner, runner, chain, chain_id, startAt, expiredAt, maxExecution, isSimulation, ... | No — frozen at creation |
| Mutable runtime state | context |
status, executionCount, lastRanAt, completedAt | Yes — updated by aggregator each execution |
| Per-execution | vm.ExecutionIndex |
Execution.index (0-based sequential counter) | Scoped to single execution, not Task-level |
| Deprecated | workflowContext |
All 13 fields | Replaced by settings + context |
| Storage/API | Task protobuf | All fields (copied from settings at creation, context at runtime) | Backward-compatible — no protobuf schema change |
When a workflow interacts with ERC20 tokens, the aggregator needs to know each token's symbol and decimals so that context-memory can format raw on-chain values into human-readable amounts (e.g., 1500000000 → 1,500 USDC).
The client declares the token contract addresses it uses via settings.tokens. The aggregator reads this list, calls GetTokenMetadata() for each address, and includes the results in the tokenMetadata field of the context-memory summarize request.
| Field | Type | Required | Description |
|---|---|---|---|
settings.tokens |
string[] |
Recommended | Array of ERC20 token contract addresses used by this workflow |
Include settings.tokens whenever your workflow interacts with ERC20 tokens:
- Event triggers watching Transfer/Approval events on token contracts
- ContractWrite nodes calling
approve,transfer, etc. - ContractRead nodes reading
balanceOf,allowance, etc. - Uniswap swap workflows (input/output tokens)
Without settings.tokens, context-memory falls back to default 18 decimals, which produces incorrect formatting for tokens like USDC (6 decimals) or WBTC (8 decimals).
Event trigger watching USDC transfers:
{
"inputVariables": {
"settings": {
"name": "USDC Transfer Alert",
"runner": "0xeCb88a770e1b2Ba303D0dC3B1c6F239fAB014bAE",
"chain": "sepolia",
"chain_id": 11155111,
"tokens": [
"0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238"
]
}
}
}Uniswap swap with two tokens:
{
"inputVariables": {
"settings": {
"name": "Stoploss USDC->WETH",
"runner": "0x5d814Cc9E94B2656f59Ee439D44AA1b6ca21434f",
"chain": "ethereum",
"chain_id": 1,
"tokens": [
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
]
}
}
}Multiple tokens in a DeFi workflow (AAVE health factor alert):
{
"inputVariables": {
"settings": {
"name": "AAVE Health Factor Alert",
"runner": "0x5d814Cc9E94B2656f59Ee439D44AA1b6ca21434f",
"chain": "ethereum",
"chain_id": 1,
"tokens": [
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"0xdAC17F958D2ee523a2206206994597C13D831ec7",
"0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599"
]
}
}
}In buildRequest() (summarizer_context_memory.go), the aggregator:
- Reads
settings.tokensfromvm.vars["settings"] - For each address, calls
TokenEnrichmentService.GetTokenMetadata(addr) - Adds results to the request-level
tokenMetadatamap (keyed by lowercase address) - Skips addresses already resolved from per-step inference (deduplication)
- Silently skips if
TokenEnrichmentServiceis unavailable (no error)
This supplements — not replaces — the existing per-step token metadata inference (from CONTRACT_WRITE configs and uniswapv3_pool.tokens). settings.tokens is the preferred explicit mechanism; per-step inference remains as a fallback for older workflows.