| name | Create decision-pack Programmatically |
| description | How to create a dlab decision-pack directory using generate_dpack() from Python code |
Creating a decision-pack Programmatically
Reference: dlab/create_dpack.py
What Is a decision-pack
A decision-pack is a directory that defines everything needed to run an agent in a Docker container:
my-dpack/
config.yaml # Name, model, hooks
.env.example # Required API keys
.gitignore # Excludes .env
docker/
Dockerfile # Container setup
requirements.txt # Dependencies (or environment.yml / pixi.toml)
modal_app/ # (optional) Modal serverless compute
my_dpack_lib/ # (optional) Custom Python library (python_lib=True)
tests/ # (required with a custom lib) pytest suite for the lib
opencode/
opencode.json # Permissions and default agent
agents/
orchestrator.md # Main agent system prompt
example-worker.md # (optional) Subagent
tools/ # (optional) Custom TypeScript tools
skills/ # (optional) Knowledge files
parallel_agents/ # (optional) Parallel agent configs
If the pack bundles a custom Python library (python_lib=True), it must also ship a pytest suite in <pack>/tests/ covering the library's deterministic logic (loaders, numeric routines, invariants). generate_dpack() does not scaffold this yet — create it after generation. Reference layout: decision-packs/mmm/tests/ (conftest, fixtures, pytest.ini).
generate_dpack()
from pathlib import Path
from dlab.create_dpack import generate_dpack
dpack_path = generate_dpack(
output_dir=Path("."),
config={
"name": "my-dpack",
"description": "dlab decision-pack: my-dpack",
"docker_image_name": "dlab-my-dpack",
"default_model": "opencode/big-pickle",
"requires_data": True,
"requires_prompt": True,
"cli_name": "",
"package_manager": "pip",
"base_image": "python:3.11-slim",
"agent_name": "orchestrator",
"agent_description": "Main orchestrator for my-dpack",
"skeletons": {
"skills": True,
"tools": True,
"subagents": True,
"parallel_agents": True,
},
"permissions": {
"bash": "allow",
"edit": "allow",
"webfetch": "allow",
"websearch": "allow",
"external_directory": "allow",
"task": "allow",
"skill": "allow",
"codesearch": "allow",
"lsp": "deny",
"todoread": "allow",
"todowrite": "allow",
},
"python_lib": False,
"python_lib_name": "",
"modal_integration": False,
"selected_skills": [],
"overwrite_existing": False,
},
on_progress=print,
)
Config Keys Detail
config.yaml
Generated with these keys:
name, description, docker_image_name, default_model, requires_data, requires_prompt
hooks section: active pre-run: deploy_modal.sh when modal enabled, commented template otherwise
Package Managers
| Manager | Base Image | Env File | Notes |
|---|
pip | python:3.11-slim | requirements.txt | Simplest |
conda | continuumio/miniconda3:latest | environment.yml | Scientific Python |
uv | python:3.11-slim | requirements.txt | Fast pip replacement |
pixi | debian:bookworm-slim | pixi.toml | Modern conda-forge |
When modal_integration=True, modal is automatically added to the env file.
Agent .md Frontmatter (tools section)
The tools section in orchestrator.md depends on skeleton selections:
| Skeletons | Tools Block |
|---|
parallel_agents=True (with or without subagents) | parallel-agents: true only |
subagents=True only | read: true, edit: true, bash: true, task: true |
| Neither | read: true (placeholder) |
Permissions
Defined in CONFIGURABLE_PERMISSIONS (list of tuples). First 6 are high-impact:
webfetch, websearch, bash, edit, external_directory, task
Remaining are internal: skill, codesearch, lsp (default deny), todoread, todowrite
Hardcoded (always set): read, glob, grep, list = allow; question = deny
Custom Tools (TypeScript)
Custom tools in opencode/tools/ MUST use execute, not run:
import { tool } from "@opencode-ai/plugin"
export default tool({
description: "What this tool does",
args: {
input: tool.schema.string().describe("Input description"),
},
async execute(args) {
const result = await Bun.$`python -c "print('hello')"`.nothrow()
const stdout = result.stdout.toString()
const stderr = result.stderr.toString()
if (result.exitCode !== 0) {
return `ERROR (exit code ${result.exitCode}):\n${stderr}`
}
return stdout.trim()
},
})
CRITICAL rules for custom tools:
- MUST use
execute, not run — OpenCode calls def.execute(args, ctx) internally. Using run causes def.execute is not a function at runtime.
- **Use
Bun.$\...`for CLI commands** — tools run inside OpenCode's Bun runtime. Use.nothrow()` to handle non-zero exit codes gracefully.
- Always check
result.exitCode — return errors as strings so the agent can diagnose issues.
Modal Integration
When modal_integration=True, generates:
docker/modal_app/__init__.py + example.py with hash-based cache busting
deploy_modal.sh pre-run hook (respects DLAB_RUN_MODAL_TOOL_LOCALLY env var — skips deploy when set to 1)
opencode/tools/run-on-modal.ts (if tools skeleton enabled) — uses modal.Function.from_name("{name}-compute", "run_compute")
modal added to env file
MODAL_TOKEN_ID + MODAL_TOKEN_SECRET in .env.example
The generated deploy_modal.sh hook checks DLAB_RUN_MODAL_TOOL_LOCALLY (default: 1 = local). Set to 0 in the .env file to enable Modal cloud execution. The hook also checks for Modal tokens and skips deployment if they're missing. decision-packs can rename this variable to something domain-specific (e.g., the MMM dpack uses DLAB_FIT_MODEL_LOCALLY).
Note: All environment variables starting with DLAB_ are automatically forwarded from the host to the Docker container by the dlab CLI. decision-packs can define their own DLAB_* variables for configuration without any framework changes.
.env.example
Auto-generated from the selected model's provider. Uses get_provider_env_vars(model_id) which checks:
- Cached provider env vars from models.dev API
- Fallback
KNOWN_PROVIDER_ENVS dict (anthropic, openai, opencode, google, deepseek, etc.)
Skills from Decision Hub
Pass selected_skills as a list of dicts with org_slug and skill_name. These are downloaded via the Decision Hub API and extracted into opencode/skills/.
Creation Process
- Validates decision-pack name (alphanumeric, hyphens, underscores)
- Creates everything in a temp directory (
tempfile.TemporaryDirectory)
- On success: atomically moves to final location (with overwrite support)
- On failure: temp dir is auto-cleaned
Helper Functions
from dlab.create_dpack import (
validate_dpack_name,
filter_models,
get_model_list,
get_provider_env_vars,
fetch_models_from_api,
ask_skills,
)