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.
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-validatedAuthoring 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.
---
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).
# 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()
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 resultctx.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 -Iwith JSON over stdin/stdout, a minimal env, a working directory confined to the skill'sscripts/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.