Skip to main content
Strategies are reusable Python DAG pipelines that run inside a sidecar process. DuckDB is the engine. Data staged from MCP backends lands as Parquet, gets loaded into a per-run DuckDB instance, and every @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.py are separate from binding.yaml
  • Contracts — full contract.yaml schema reference
  • Bindings — full binding.yaml schema, fetch modes, pagination
  • Response Adapters — parsing non-JSON tool responses
  • Lifecycle — discovery, hot reload, governance status
The rest of this page is the framework reference — authoring paths, directory layout, the contract and binding schemas at a glance, the Python step framework, data flow, and the MCP tools agents use to interact with strategies.

Authoring Paths

There are two supported ways to create strategies:
  1. Manual filesystem authoring — create contract.yaml, strategy.py, and optional binding.yaml under strategies/<domain>/<category>/<slug>/ (e.g. strategies/security/enrichment/splunk_field_survey/).
  2. MCP-driven creation — call strategy_create with Python code plus either:
    • contract (preferred, YAML string for contract.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

The runner walks 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

The contract.yaml defines what the strategy does, what parameters it accepts, and what data it needs.

Required Fields

Parameters

Types are validated and coerced at runtime: float64 → int, string → bool, etc.

Data Requirements

Column types: Any valid DuckDB type — 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 optional binding.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)

Background staging is used for large paginated fracta_mcp_gateway fetches. The current heuristic is:
  • pagination is configured
  • and max_rows > 50000
When that path is selected, the agent receives {"status": "staging"} and can poll with the session_id.

Python Framework

Strategy Base Class

Every strategy is a Python class that extends Strategy:

@step Decorator

Marks a method as a pipeline step. Steps run in topologically sorted order.
Dependencies are inferred from parameter names:
For explicit ordering, use depends:

StrategyContext

Every step receives ctx with:

Querying Staged Data

Tables declared in requires.tables are loaded into DuckDB from Parquet before execution:

Querying the Knowledge Graph

When requires.graph: true:

Mid-Execution Tool Calls (ctx.mcp)

When the gateway is configured and strategy.gateway_access: true, strategies can call MCP tools during execution for targeted follow-up queries:
When to use 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 in binding.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

Each run gets a unique 8-character hex ID. Parquet files are cleaned up after execution. Two concurrent runs of the same strategy never share DuckDB or Parquet state.

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:
Partial results are capped at 16 KB per step and 64 KB total.

Structured Errors

Strategy errors include classification for client-side handling:
Categories: 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:
For hot reload (deploying a new or updated strategy to a running cluster without rebuilding) and the dual-container split between strategy-runner and fracta-gateway, see Lifecycle.