@step reads through ctx.duckdb. Joins, aggregations, and correlations happen at native speed in columnar storage — no LLM, no per-row Python loops. Strategies return structured results.
A strategy lives as three files: contract.yaml (what data it needs), strategy.py (the DAG of steps), and an optional binding.yaml (how to fetch the data in your environment). The first two are publishable and portable; the binding plugs the strategy into your specific stack.
In this section
- Quick Start — build a minimal end-to-end strategy with all three files
- Portability — why
contract.yaml+strategy.pyare separate frombinding.yaml - Contracts — full
contract.yamlschema reference - Bindings — full
binding.yamlschema, fetch modes, pagination - Response Adapters — parsing non-JSON tool responses
- Lifecycle — discovery, hot reload, governance status
Authoring Paths
There are two supported ways to create strategies:- Manual filesystem authoring — create
contract.yaml,strategy.py, and optionalbinding.yamlunderstrategies/<domain>/<category>/<slug>/(e.g.strategies/security/enrichment/splunk_field_survey/). - MCP-driven creation — call
strategy_createwith Python code plus either:contract(preferred, YAML string forcontract.yaml)metadata(legacy JSON path)
strategy_create writes the strategy files through the sidecar and can also register governance nodes in the graph when the graph is connected.
Use manual authoring when iterating locally in the repo. Use strategy_create when an agent or operator is compiling exploratory work into a reusable strategy through fracta itself.
Directory Layout
strategies/ recursively on every call and picks up any directory with both contract.yaml and strategy.py. Nesting depth is up to you — the runner uses os.walk, so strategies/security/enrichment/splunk_field_survey/ works the same as a flatter strategies/enrichment/splunk_field_survey/. See Lifecycle for the full discovery and hot-reload rules.
The convention is domain first, then category. <domain> is your top-level grouping (e.g. security, infra, finance); <category> is the strategy type within that domain. The directory layout is purely organizational — a strategy’s identity is the name field in its contract.yaml, not its path.
Contract Reference
Thecontract.yaml defines what the strategy does, what parameters it accepts, and what data it needs.
Required Fields
Parameters
Data Requirements
VARCHAR, INTEGER, BIGINT, DOUBLE, TIMESTAMP, BOOLEAN, etc.
Semantic tags: Optional hints that enable the auto-resolve pipeline to match columns to MCP backend fields. Without semantic tags, a binding.yaml is required for auto-staging.
Backend Pinning and Discovery Hints
pinned_backend tells the resolver which MCP backend to use. mcp_hints are advisory — they guide the orchestrator and auto-resolve pipeline.
Binding Reference
The optionalbinding.yaml maps contract table requirements to concrete MCP data sources. Required when columns lack semantic tags for auto-resolve.
Fetch Modes
Query Templates
Templates use{{param_name}} placeholders resolved from strategy params:
Field Mapping
field_map renames source fields to match contract column names:
Pagination (for large tables)
fracta_mcp_gateway fetches. The current heuristic is:
paginationis configured- and
max_rows > 50000
{"status": "staging"} and can poll with the session_id.
Python Framework
Strategy Base Class
Every strategy is a Python class that extendsStrategy:
@step Decorator
Marks a method as a pipeline step. Steps run in topologically sorted order.depends:
StrategyContext
Every step receivesctx with:
Querying Staged Data
Tables declared inrequires.tables are loaded into DuckDB from Parquet before execution:
Querying the Knowledge Graph
Whenrequires.graph: true:
Mid-Execution Tool Calls (ctx.mcp)
When the gateway is configured andstrategy.gateway_access: true, strategies can call MCP tools during execution for targeted follow-up queries:
ctx.mcp vs pre-staging:
See Runtime API for full documentation.
Data Flow
DuckDB is the engine
DuckDB is a core construct of the strategies framework, not an implementation detail. Every strategy run gets a fresh in-process DuckDB instance (400 MB memory, spill-to-disk enabled). All staged data lands in that DuckDB as tables. Every@step reads through ctx.duckdb. The Python in strategy.py is essentially orchestration around DuckDB queries — joins, aggregations, window functions, and CTEs run at native speed against columnar storage, not as Python loops over dicts.
This is what makes strategies cheap and deterministic. A correlation across two ten-million-row tables is a SQL join in DuckDB — milliseconds, no LLM, identical answer every time.
How data gets into DuckDB
The fetch mode declared inbinding.yaml decides who pulls the data. The choice has direct cost implications:
The three fetch modes:
The cost story is exactly the column “LLM in the loop”: if the LLM doesn’t see the rows, you don’t pay for them. A strategy that pulls a million events through
fracta_mcp_gateway and joins them in DuckDB costs the same in tokens whether it returns ten rows or zero — because the LLM only ever sees the final summary.
The runtime sequence
MCP Tools
Agents interact with strategies through these MCP tools:strategy_list does not filter by status by default; pass status="validated,promoted" (or "exploratory", "all") to filter. See Lifecycle for the governance-status model.
strategy_run Response States
Error Handling
Partial Results
When a step fails, all previously completed step outputs are preserved:Structured Errors
Strategy errors include classification for client-side handling:transient (retryable), permanent (fix required), validation (bad input), partial (strategy ran but incomplete).
Deployment
Docker Image
Strategies are baked into the Docker image at/opt/fracta/strategies/. The Dockerfile copies the strategies/ directory:
strategy-runner and fracta-gateway, see Lifecycle.
