Integrations API
Reference for the shared adapter layer and the four framework adapters: signatures, mapping, error behaviour, replay, observability and the version each was written against. The usage these support is on agent integrations.
The shared layer
tradefloor.integrations.common holds what every adapter needs and no framework owns: the observation allowlist, the decision schema and the model derived from it, two-stage validation, transcripts and replay, and the base an adapter completes.
| NAME | WHAT IT IS |
|---|---|
| serialize_observation | The observation allowlist. An adapter is handed this payload and never the Observation, which carries .engine and with it fair value, the nine-way attribution of every move, each company's mispricing and the macro path the run has not reached. |
| decision_schema | The canonical contract. decision_model derives the Pydantic model from it. |
| parse_decision | Stage one: the answer against the schema. Unknown keys are refused by name, at the top level and on an action. |
| orders_from | Stage two: what survives against this market. Every symbol against the listed universe, every side against BUY, SELL and HOLD, and the size against the participation cap, which clips and records the clip. |
| Transcript | Record and replay. Each exchange is keyed by a digest of the exact input the framework was sent. |
| run_sync | The one supported async bridge. It runs a coroutine on another thread, so context variables set by the caller do not propagate into it. |
| FrameworkAdapter | The base an adapter completes. ReplayMixin supplies the record-and-replay branch. Shared constructor arguments: info, every, fundamentals, max_participation and arm. |
The module also exports five constants, OBSERVABLE_MACRO, SIDES, HISTORY_STEPS, MAX_PARTICIPATION and DECISION_SCHEMA_VERSION; three classes, Action, Decision and AdapterInfo; and three functions, require, digest and replay_response.
Error behaviour
Every error below is a subclass of tradefloor.ValidationError. The split that matters is between an agent that answered badly and a framework that failed, because filing one as the other makes a reliably poor agent read as an unreliable network.
| NAME | MEANING |
|---|---|
| IntegrationError | The family root. |
| MissingDependencyError | Also an ImportError. Raised by require(). |
| FrameworkError | The framework call itself failed: a timeout, a transport failure, a budget stop. |
| DecisionError | Output the parser cannot read as a decision. Each one names the step and the day. |
| MarketRefusalError | A DecisionError. Well formed, and this market cannot take it. |
No adapter converts a failure into an empty decision. An empty decision scores as trades=0 beside an empty error column, which is what a considered decline looks like, so a failed run wearing that shape would record a choice nobody made.
Replay and recording
A Transcript keys each exchange by a digest of the exact input the framework was sent, never by a step number. Change the roster, the seed, the cadence or the instructions and the key goes missing: the run stops and names the step it stopped at. Keyed by position, a replay would answer the new question with the answer given to the old one, and nothing in the output would say so.
adapter.record carries one entry per decision: the digest, the exact input, the raw response, the validated decision, the orders and any participation clips. The chain from observation to order sits in one place in a replayed run as much as a live one.
A recording is keyed to the market it was taken against, so a move in the default preset invalidates every recording. Record the preset beside the transcript.
Generic callable
CallableAgentAdapter puts a plain Python function in the shape evaluate runs, and callable_agent(fn, **kwargs) is the convenience constructor. An async function is driven through common.run_sync. The function is handed the serialized payload, never the Observation.
OpenAI Agents SDK adapter
OpenAIAgentsAdapter(agent, *, mode="replay", transcript, recorder, model, brief, max_turns=6, tracing=False, run_id, ...). The convenience constructor openai_agent(agent, **kwargs) defaults mode="live". Also exported: payload_of(call), BRIEF, DISTRIBUTION and EXTRA. Methods beyond the base: ask(obs, payload) and input_items(payload). state() adds max_turns and brief_digest, since both change what a decision can be.
Compatibility
The decision contract is bound as the output type on Agent.clone(...), so your agent is copied rather than mutated and its instructions, tools, model settings, hooks, handoffs and guardrails survive. An agent that already declares its own output_type is refused with a message naming the way to opt in. A run that ends on a different agent through a handoff is refused by name, since that agent carries an output type of its own.
Runtime behaviour
The adapter calls Runner.run through the shared bridge. Runner.run_sync cannot run inside an existing event loop, measured on 0.22.0, so it is unusable from a notebook. max_turns bounds one decision at six model calls, below the SDK's default of ten, which suits interactive use; a loop running at every cadence step of every arm wants the lower bound. Installing the extra brings 37 transitive packages, mcp among them, and import agents costs about four and a half seconds; the adapter imports it inside the method that needs it, so a replayed run pays none of that.
Retry and validation behaviour
On a malformed answer the SDK makes one model call and raises ModelBehaviorError with no client-side retry, measured on 0.22.0. Binding the decision model puts the side enum, the non-negative quantity and additionalProperties: false into the schema the provider is shown, which makes an invalid decision less likely to be generated. It adds no repair loop. Size error handling for this adapter assuming no retry exists.
Error behaviour
Six SDK exceptions are outcomes rather than failures and each becomes a DecisionError: ModelBehaviorError, ModelRefusalError, MaxTurnsExceeded and all four guardrail tripwires. The SDK redacts model text from its own messages by default, so its message alone cannot say which decision point failed. A tripped guardrail is not converted to a hold. Everything else stays a FrameworkError with the chain intact.
Tracing
SDK tracing is on by default and exports to OpenAI. Every run this adapter starts passes tracing_disabled=True unless tracing=True was given, per run rather than through the SDK's process-global switch, so it leaves tracing alone for other code in the process.
Version notes
The floor is openai-agents>=0.22, the version the adapter was written against. A floor at the major would admit releases the adapter has never met. The package imports as agents, supports Python 3.10 through 3.14, and Tradefloor needs 3.11 or later, so there is no version pinch.
PydanticAI adapter
PydanticAIAdapter(agent, *, deps, mode="live", model, transcript, recorder, instructions, bind_output_type=True, request_limit=8, ...). Also exported: UsageLimitReached, render(payload), MANDATE and MANDATE_VERSION.
Compatibility
The agent arrives built and leaves unmodified. Its deps_type, tools, RunContext usage, instructions, toolsets and output type keep working, and deps reaches run(deps=...) verbatim. PydanticAI has a single dependency slot, read by every tool through RunContext.deps and carrying no runtime type check, so an adapter that put its own payload there would hand your tools an object of the wrong type. The adapter puts nothing in it, and the consequence is that a tool cannot query the observation from inside a decision: a tool that needs the day's prices reads them from a holder on your own deps object.
Decision mapping
The shared decision model is bound as the run's output type, so the side enum, the non-negative share count and the required actions list enter the schema the model is shown. The binding is per run and your agent's own output type is untouched. An agent carrying an @agent.output_validator cannot have its output type overridden, which PydanticAI forbids by design; the adapter says so and names bind_output_type=False as the way through, after which your output type stands and Tradefloor still validates what it produces.
Retry and validation behaviour
A schema violation is caught inside PydanticAI's own retry loop and corrected within the turn. UnexpectedModelBehavior is filed as a DecisionError: an agent that spent its retry budget without producing a valid decision answered badly. A run that hits its request budget raises UsageLimitReached, a FrameworkError subclass, so a deliberate budget stop can be caught by name.
Runtime behaviour
An offline model goes in the adapter's model= argument rather than through Agent.override, which is built on context variables that do not propagate across the shared async bridge. A test suite can add models.ALLOW_MODEL_REQUESTS = False, which raises a plain RuntimeError, so a test expecting a framework exception will miss it.
Observability
PydanticAI instruments nothing by default and the adapter turns nothing on. In the supported version, instrumentation is configured through the framework's own current APIs: logfire.configure() with logfire.instrument_pydantic_ai(), or Agent.instrument_all(). There is no instrument= constructor argument in 2.36.0. Measured with Logfire: instrumentation activates, the run completes unaffected, and the spans export with a clean force_flush(). Arrival in the Logfire backend was left unchecked, since reading it back needs a read token.
Version notes
The floor is pydantic-ai-slim>=2.36. Slim provides the pydantic_ai module without the provider SDKs the umbrella package adds, none of which any adapter imports. TestModel and FunctionModel are both in slim.
LangGraph adapter
LangGraphAdapter(runnable, *, mode="live", transcript, recorder, input_builder, output_parser, instructions, config, thread_id, ...), with langgraph_agent(runnable, **kwargs) as the convenience constructor. Also exported: default_input_builder, default_output_parser, render, GraphInterruptedError, INSTRUCTIONS, DEFAULT_INPUT_KEYS and INTERRUPT_KEY.
Compatibility
The adapter takes anything with an invoke method, duck-typed rather than checked by class. Runnable is a nominal ABC with no __subclasshook__, so an isinstance check would reject a plain object with a working invoke, which is the shape a deterministic double takes. An object with only ainvoke is driven through common.run_sync. An uncompiled StateGraph is refused by name.
Input mapping
The default input carries both shapes at once, observation for a structured graph and messages for the MessagesState shape. A key a graph's state schema does not declare is dropped before any node runs, so one default serves both. A TypedDict state gets no schema validation at the graph boundary, so a mismatch surfaces as an IndexError or a bare KeyError inside your own node. Use input_builder when the graph expects a different state schema.
Output mapping
A graph returns its whole state, so unwrapping it belongs to the adapter. The default parser reads a Decision, an interrupted state, a state carrying actions, a state carrying decision, or the MessagesState shape, and refuses anything else by name.
Interrupts
An interrupt raises GraphInterruptedError, a DecisionError subclass, naming the question that went unanswered. An interrupt means pause and resume later, and a market loop has nowhere to resume into: the book, the macro path and the variance process advance the moment act returns. Measured on 1.2.11, a GraphInterrupt never escapes invoke and arrives inside the state as __interrupt__, which is why the parser checks for it ahead of decision: a checkpointed thread can carry a decision written on an earlier step. Run a human-in-the-loop graph separately, and give Tradefloor a graph that decides.
Observability
Run identity rides config["metadata"] and a "tradefloor" tag, merged into any RunnableConfig you pass. Metadata is the hook because a node receives tags, metadata and recursion_limit from the invoking config while run_name is consumed by the tracer. LangSmith tracing stays off unless one of its environment variables reads true. Measured against a live account, two decisions produced twelve exported runs and all twelve carried tradefloor_run_id; the adapter also stamps tradefloor_arm, tradefloor_day, tradefloor_step and tradefloor_decision_schema per decision. Turning tracing on sends the rendered observation to LangSmith.
Version notes
The floor is langgraph>=1.2, one name because langchain-core is a hard dependency of it. LangGraph needs Python 3.10 or later, so the overlap with Tradefloor is 3.11 upward. create_react_agent is deprecated in LangGraph 1.x and points at a distribution this extra does not install; a plain StateGraph carries no deprecated import and the adapter duck-types both the same way.
FinRobot adapter
The FinRobot integration predates the shared layer, which was derived from it. Its DecisionError now descends from common.DecisionError rather than directly from ValidationError. It stays importable, stays a ValidationError, and is additionally catchable as the shared error. Nothing else about the module moved.
The finrobot extra is the largest here by an order of magnitude and pins Python 3.11 exactly, since FinRobot declares >=3.10, <3.12 and Tradefloor needs >=3.11. Replaying a recorded run needs none of it.