This guide reflects the current codebase and is updated frequently while Noēsis is in active development.
How adapters work
An adapter is any execution target that Noēsis can invoke.The ns.solve() integration contract
ns.solve() invokes the resolved using= target in the act phase by calling invoke(), then run(), then __call__() (in that order). EpisodeRunner owns cognition, governance, and event emission.
If you’re already inside an event loop, use ns.solve_async(...), which preserves the same invocation order and semantics while allowing async adapters to be awaited.
The execute(...) adapter protocol (advanced)
The repo also defines an Adapter.execute(...) protocol in noesis.adapters.protocols.Adapter:
ns.solve() does not call execute() directly.
When you call ns.solve() with using=, Noēsis:
- Resolves the target via the graph loader (callable, object, or string source)
- Captures the task in the observe phase
- Runs the target in the act phase
- Records the result in the reflect phase
- Emits all events to the timeline
Resolution rules
using= accepts several shapes, resolved by the loader:
- Callable: used directly (or invoked if it is a zero-arg factory).
- Object: used directly if it exposes
invoke()or is callable. - String: can be a dotted factory (
pkg.mod:make), a filesystem path, or a short name resolved viaflows.<name>ornoesis_user.<name>.
invoke(), run(), or __call__() (in that order).
Objects that only define execute() are not compatible with ns.solve() unless wrapped.
If you need to map the input task string to a structured payload, you can:
- wrap the target and do the mapping inside your wrapper, or
- if supported by the invocation path you’re using, define a
__noesis_input_mapper__callable that transforms the task before invocation.
Plain functions
The simplest adapter is a plain Python function:LangGraph integration
Compiled LangGraph graphs exposeinvoke(), so you can pass them directly:
noesis.adapters.langgraph.LangGraphAdapter. It resolves awaitables with asyncio.run() when no event loop is running (and raises if a loop is already running). It also supports an optional input mapper and may fall back to __noesis_input_mapper__ if the wrapped graph provides one.
CrewAI integration
Wrap a CrewAI crew withnoesis.adapters.crewai.CrewAIAdapter:
{"task": task} unless the crew defines __noesis_input_mapper__.
If your crew expects a different input shape, provide __noesis_input_mapper__ on the crew or pass input_mapper= when constructing CrewAIAdapter.
Claude Agent SDK integration
When you’re already running inside an event loop, usens.solve_async(...) and pass an async adapter directly.
If you’re in synchronous code, wrap the SDK’s async loop with
asyncio.run(...) or move the call
to a worker thread before calling ns.solve(...).SDK surface shown above reflects the public
claude_agent_sdk examples; verify against your installed SDK version.Experimental Assistants adapter
noesis.adapters.assistant.AssistantsAdapter is experimental and writes its own events via write_event. It follows the execute(...) protocol but does not expose invoke() / run() / __call__(), so it is not compatible with ns.solve() without a wrapper. Use it only with a custom runner that explicitly calls execute().
Custom adapters with metadata
Adapters can return any object. Noēsis usesstr(result) when constructing the action summary. If you return a dict, the summary will include its string representation.
Error handling
Noēsis treats exceptions as failures. Raise to mark a failed action:Using string sources
You can also pass a stringusing= value that the loader resolves:
pkg.mod:factoryto import and call a zero-arg factory./path/to/module.pyto load a local modulenameto loadflows.nameornoesis_user.name
Adapter best practices
Keep adapters stateless. If you need state, use the Noēsis state artifact rather than adapter instance variables.

