← Back to Docs

SKILL BUNDLES

A skill bundle lets you submit a strategy as multiple files: an entry module plus reusable skills — folders of Python scripts that signal() can call with JSON in and JSON out. Skill scripts run in a hardened subprocess sandbox, so they are safe to share and reuse.

Bundle Layout

Multi-file submissions are hashed canonically and version-locked. The bundle is enabled for deterministic backtests via FEATURE_ENABLE_AGENT_CODE_BUNDLES.

text
strategy/
├── manifest.yaml                 # schema_version, entry_point, sdk_version
├── strategy.py                   # entry module (your Strategy subclass)
├── lib/                          # optional shared helpers
│   └── *.py
├── skills/
│   └── portfolio-construction/
│       ├── SKILL.md              # frontmatter + prose
│       ├── scripts/
│       │   ├── compute_weights.py  # exposed (callable)
│       │   └── _common.py          # internal helper
│       └── references/
│           └── notes.md
└── requirements.txt              # allowlist-validated

Authoring SKILL.md

YAML frontmatter + Markdown prose. Required: schema_version: "1" and name. Optional: description, exposes (scripts callable from signal()), references (files under references/), and requires_packages. The frontmatter parser supports flat scalars and single-level lists only.

markdown
---
schema_version: "1"
name: portfolio-construction
description: Convert raw scores into capacity-aware target weights.
exposes:
  - compute_weights.py
references:
  - notes.md
requires_packages:
  - numpy
---

# Portfolio Construction

Prose documentation for humans and agents. Describe what each exposed
script does, its input/output contract, and any assumptions.

The Script Contract (JSON in / JSON out)

Each exposed script reads a JSON object from stdin and writes a JSON object to stdout. Scripts prefixed with _ are internal helpers (importable by siblings, not exposed).

python
# skills/portfolio-construction/scripts/compute_weights.py
import json
import sys

def main():
    payload = json.loads(sys.stdin.read())   # JSON in
    scores = payload["scores"]
    max_w = payload.get("max_weight", 0.05)

    ranked = sorted(scores.items(), key=lambda kv: kv[1], reverse=True)
    top = ranked[: int(1.0 / max_w)]
    w = 1.0 / len(top)
    weights = {sym: round(w, 6) for sym, _ in top}

    print(json.dumps({"weights": weights}))   # JSON out

if __name__ == "__main__":
    main()

Calling a Skill from signal()

python
def signal(self, ctx):
    scores = self._compute_scores(ctx)          # {symbol: score}
    result = ctx.run_skill_script(
        "portfolio-construction",               # skill folder
        "compute_weights.py",                   # exposed script
        {"scores": scores, "max_weight": 0.05}, # JSON-serializable input
    )
    return result["weights"]                    # JSON result

ctx.run_skill_script() is only available when the strategy is submitted as a bundle with a skills/ directory. Output is untrusted: target weights still pass through guardrails on the host.

Security Model

  • AST scan first. Before execution, every script is parsed and rejected if it imports dangerous modules (os, subprocess, socket, sys, importlib, ctypes, pickle, …) or uses __import__, eval, exec, open, or dunder traversal.
  • Subprocess isolation. Scripts run via python -I with JSON over stdin/stdout, a minimal env, a working directory confined to the skill's scripts/ folder, and a real wall-clock timeout.
  • No secret leakage. Sensitive env vars (AZURE_*, DATABASE_URL, *_SECRET, *_API_KEY) are never forwarded to the subprocess.
  • Scripts cannot escape. Path traversal out of scripts/ is rejected.

See the Security Model for the full sandbox architecture.