Getting Started
Making a New Project
Creating a project with fw.new() or the command line, and preparing a cloned project with bootstrap()
Create a Project from Python
new() creates a Python project from your global defaults. It takes a name, the folder to create, and a project type.
import framework as fw
fw.new("Vaccine Study", "~/projects/vaccine-study", type="project")
The type is one of project, project_sensitive, course, presentation, or bare. The Project Types guide describes each one.
fw.new("Patient Outcomes", "~/projects/patient-outcomes", type="project_sensitive")
fw.new("Stats 101", "~/teaching/stats-101", type="course")
fw.new("Q4 Review", "~/talks/q4-review", type="presentation")
In an interactive session, new() asks for any value you leave out. It suggests ~/projects/<name> as the location and bare as the type. In a script or notebook, where standard input is not a terminal, a missing name or location is an error and a missing type means bare. Pass interactive=True or interactive=False to choose the behavior yourself.
The folder must not exist yet. new() returns the path of the new project.
Workflows
The workflow decides how notebooks are written. Pass it as workflow.
| Workflow | Tools it turns on | make_notebook() writes |
|---|---|---|
jupyter |
jupyter and quarto |
Jupyter notebooks (.ipynb), with Quarto kept for rendering and publishing |
quarto |
quarto |
Quarto documents (.qmd) |
scripts |
none (it turns quarto and jupyter off) |
Python scripts (.py) |
Python projects of type project and project_sensitive use the Jupyter workflow unless you choose another. Course and presentation projects use Quarto, and bare projects have no workflow. A Scripts-only project gets no notebook files; any starter scripts come from its blueprint. The workflow and the notebook format are recorded under scaffold: in the project's settings.
fw.new("Pipeline", "~/projects/pipeline", type="project", workflow="scripts")
Tools and Sets
Tools are switches for what a project uses beyond its folders. Each tool adds only its own files, folders, and packages. tools_available() lists the tools for Python projects.
uvmanages the project's dependencies inpyproject.tomlanduv.lock. It is on for every project.quartowrites the root_quarto.ymland render settings. It is on for every type exceptbare.jupyteradds theipykernelpackage for Jupyter notebooks. The Jupyter workflow turns it on.quarto_slidesadds aslides/folder that renders as RevealJS.quarto_manuscriptadds amanuscript/folder with a Quarto manuscript to start from.
Pass tools as a list of tools to turn on, or as a map that can also turn a default off.
fw.new(
"Lab Notes", "~/projects/lab-notes", type="project", tools={"quarto": False, "jupyter": True}
)
fw.new("Q4 Review", "~/talks/q4-review", type="project", tools=["quarto_slides"])
A tool that another tool needs is turned on too, with a message. quarto_slides and quarto_manuscript turn on quarto. Turning off a tool that another one needs is an error. The project's tools are recorded under tools in settings.yml. A project without a tools section predates it and keeps using Quarto.
skill_sets and package_sets add bundled sets of AI skills and packages. The framework skill set adds Framework's four skills. The tool and set definitions are shared with the R package and Framework Cloud, so a project gets the same pieces whichever one creates it.
With the command line, --tool ID turns a tool on and --tool ID=false turns it off. --skill-set and --package-set add sets. Each option can be repeated.
Create a Project from the Command Line
The package's command-line entry point runs new() with the same defaults.
python -m framework create --name "Vaccine Study" --path ~/projects/vaccine-study --type project
Without --type, the project is bare. With --interactive, the command asks for missing answers. The command prints the project folder on success and exits with status 1 and a message on failure.
When framework-py is not installed in any environment yet, uv can run the command with a temporary copy of the package.
uv run --with framework-py python -m framework create --name "Vaccine Study" --path ~/projects/vaccine-study --type project
With fw
The fw command-line tool creates projects in every Framework language. For a Python project, it finds a Python runtime with framework-py and runs the command above.
fw new vaccine-study --lang python --type project
fw creates the project in ./<name> unless --dir names another folder.
From a Layers File
framework.pub describes a project as a layers file. --from-layers creates a project from one.
python -m framework create --from-layers layers.json --path .
This form runs headless unless you pass --interactive. A headless run asks no questions, initializes no git repository, writes nothing under .github/workflows/, and does not register the project. It works in a fresh checkout that holds only .git, .github, .framework, README.md, and LICENSE, which is how the framework.pub GitHub Action uses it. project_create_from_layers() does the same from Python.
Create a Project from Framework Cloud
A project defined on Framework Cloud is created from its project token.
fw.new(key="fw_...")
new() fetches the project's definition from the server and saves the token and server in the new project's .env as FW_PROJECT_TOKEN and FW_CLOUD_URL. The project then publishes to that server and uses its integrity ledger. The Framework Cloud guide describes what the server receives.
From Blueprints and Structures
Blueprints, structures, and sets saved on Framework Cloud can be used directly. Log in first with cloud_login(), then pass a blueprint or a structure handle.
fw.new("Lab Notes", "~/projects/lab-notes", blueprint="ada/lab")
fw.new(
"Stats 101",
"~/teaching/stats-101",
structure="framework/course",
tools=["quarto_slides"],
skill_sets=["ada/teaching"],
sensitive=False,
)
A structure replaces the blueprint's structure. tools, package_sets, skill_sets, and skills take handles, where a bare id such as quarto means Framework's. sensitive, ai, and git set those settings. Tools that Framework's tools require are added before the choices are sent.
new() sends the choices to the server, which resolves them into layers. It builds the project from those layers, then registers it on the server, which records the choices and returns a project token that new() saves in .env. If registration fails, the project is still created and new() reports why. Only the choices you pass are sent. Data and code stay on your computer.
When you are logged in, Framework Cloud resolves every choice, as it does for R and fw. A type is sent as Framework's blueprint of that name, such as framework/project, together with any workflow, tools, and sets. With no choices at all, the cloud resolves your defaults. new() saves that result in Framework's config folder, under cache/defaults/, one file per server and language, in the format R and fw share.
When the server can't be reached, new() builds your defaults from that cache and says so: "Using your Framework Cloud defaults from 2026-11-01". Otherwise, including any explicit choice and a server too old to resolve projects, an interactive session asks before building from the built-in defaults described above. A script stops with an error unless you pass offline=True. new() never switches to a different configuration silently. A rejected token is reported as an error, never treated as offline. When you are not logged in, new() uses the built-in defaults without asking.
fw.new("Field Notes", "~/projects/field-notes", offline=True)
What Gets Created
A new project contains these files, depending on its type.
- The folders of the project type.
settings.yml, plus split files undersettings/for standard and privacy-sensitive projects.pyproject.toml, which depends onframework-pyand tells uv not to build the project as a package.scaffold.py, which scaffold() runs after it loads the settings..env, with empty placeholders for PostgreSQL and S3 credentials..gitignorefor the project type. Framework makes sure it ignores.env.framework.db, the project database._quarto.ymlin the root, and_metadata.ymlin a folder that renders in another format, such as a course'sslides/.AGENTS.md, the AI assistants' skills, andCLAUDE.mdwhen Claude Code is selected.- A
.code-workspacefile for VS Code and Positron. presentation.qmdin presentation projects..framework/spec.json, the resolved definition the project was built from.
A bare project gets only settings.yml, pyproject.toml, .gitignore, and .framework/spec.json.
After creation, new() does three more things.
- It initializes a git repository when git is installed and your defaults allow it. It installs the pre-commit hook when a hook check is enabled. It makes the first commit when git's
user.nameanduser.emailare set, and skips the commit with a note otherwise. - It records the project in the registry in your global settings, unless the folder is in the system's temporary folder.
- When you are logged in to Framework Cloud, it registers the project there with its name, type, and language, and saves the project token in
.env. Bare projects are not registered.
Install the packages before you start work.
cd ~/projects/vaccine-study
uv sync
Adopt an Existing Project
adopt() turns a folder you already have into a Framework project, in place. It lists the folder's subfolders, at most three levels deep, and proposes a role for each: a Framework directory key such as inputs_raw, or a folder of its own under extra_directories. It reads folder names only, never files or their contents. Hidden folders, version control, environments such as .venv and renv, and caches are left out.
fw.adopt("~/projects/old-study")
You review the proposal before anything is written. Press Enter to accept it, type a folder's number to change its role, or type - and the number to leave it out. Then adopt() writes settings.yml and .framework/spec.json. It moves nothing and creates nothing else, and it refuses a folder that is already a Framework project. The project is bare, with flat settings and no tools.
A folder whose path contains "private" gets a warning: "'data/private' looks private. Keep it out of git and away from AI assistants." The review then asks whether to turn on the sensitive-data setting. In a script, pass yes=True to accept the proposal; the sensitive setting stays off. From a shell, run python -m framework adopt --path DIR, with --yes to skip the review. The R package and fw map folders with the same shared rules.
When you are logged in, adopt() finishes by offering to register the project on Framework Cloud and to save the folder layout as a structure. structure_import() does the second step on its own. It sends only the folder paths, and the structure is private to you.
fw.structure_import("~/projects/old-study", name="Lab layout")
Prepare a Cloned Project
A clone lacks the files git does not carry: .env, framework.db, ignored and empty folders, and the virtual environment. bootstrap() restores them. Every step is safe to repeat, and none contacts Framework Cloud.
- It makes sure
.gitignoreignores.env. - It copies
.env.exampleto.envwhen.envis missing, and lists variables that are still empty. - It creates the folders listed in the settings. Entries that name a file, such as
presentation.qmd, are skipped. - It creates
framework.db, or adds tables a newer version needs. - It installs the packages with
uv sync, using--frozenwhenuv.lockexists. - It installs the pre-commit hook when a hook check is enabled.
Run it from the command line after cloning.
git clone https://github.com/example/vaccine-study.git
cd vaccine-study
uv run python -m framework bootstrap
The command takes --path for another folder, --interactive to ask for the values of empty .env variables, and --no-packages to skip the uv step. Secret-looking variables are asked for without echoing the answer.
From Python, bootstrap() returns a BootstrapResult with the steps it took and the work left for you.
result = fw.bootstrap("~/projects/vaccine-study", packages=False)
result.todo
To share the names of the variables a project needs, commit a .env.example file with empty values. bootstrap() refuses projects whose language is not python.
Building from a Spec
new() builds a project spec from three layers: the package defaults, your global settings, and the type you chose. Tools that need finer control can use the same steps directly.
- spec_resolve() merges layers into a complete spec and reports keys that more than one layer set.
- spec_check() lists the problems that would stop creation.
- project_create() builds a project from a resolved spec.
resolved = fw.spec_resolve(
[{"kind": "project", "layer": {"type": "course", "language": "python"}}],
name="Stats 101",
)
fw.project_create(resolved.spec, "~/teaching/stats-101")
A layer that sets a language other than python is refused, because framework-py builds Python projects only.