ESC

Type to search...

Context

Every processor receives one argument: a Context. It’s a mutable data bag — no methods, no logic, just fields.

from evoid.core import Context

async def my_processor(ctx: Context) -> dict:
    # Read intent metadata
    name = ctx.intent.metadata.get("name")

    # Read/write shared state
    ctx.state["step"] = "done"

    # Access injected deps
    db = ctx.deps.get("db")

    return {"ok": True}

Fields

FieldTypeWhat it is
ctx.intentIntentThe intent that triggered this pipeline. Immutable.
ctx.statedictShared state between processors. Read/write.
ctx.depsdictInjected dependencies (db, cache, etc). Set by adapters/plugins.
ctx.metadatadictCopy of intent metadata. Writable without touching the intent.
ctx.errorslistAccumulated errors. Processors can append, never clear.
ctx.idstrAuto-generated unique ID for this execution.

Data Flow

Processors communicate through ctx.state. One writes, the next reads:

async def check_inventory(ctx: Context) -> dict:
    sandwich = ctx.intent.metadata.get("sandwich")
    ctx.state["in_stock"] = sandwich in ["BLT", "Club", "Veggie"]
    return {"checked": True}

async def create_order(ctx: Context) -> dict:
    if not ctx.state.get("in_stock"):
        return {"error": "Out of stock"}
    return {"status": "created"}

Think of it as a conveyor belt. Each station reads what the previous one wrote.

ctx.deps vs ctx.state

  • state: Your working memory. Changes every request. Processor-to-processor.
  • deps: Your tools. Set once by the adapter or plugin. Database connections, caches, config. Processors read but don’t rewrite.
# Adapter sets deps (once, at startup)
ctx.deps["db"] = sqlite3.connect("shop.db")
ctx.deps["cache"] = redis_client

# Processors use deps (every request)
async def get_menu(ctx: Context) -> dict:
    cache = ctx.deps.get("cache")
    cached = cache.get("menu") if cache else None
    if cached:
        return json.loads(cached)
    # ... fetch from db

fork()

Create a child context for parallel execution. Same intent, copy of state and deps:

from evoid.core import fork

async def parallel_work(ctx: Context) -> dict:
    child = fork(ctx)
    child.state["branch"] = "left"
    # ctx.state is unchanged — child has its own copy

Why Not Just Use kwargs?

Because processors are pure functions. They take one thing in, one thing out. Context is that one thing. No magic signatures, no argument ordering guessing — just ctx.