Skip to content
Foundry
Esc
↑↓navigate↵open⌘Jpreview
On this page

Definitions and instances

Separate declaring work, placing it in a workflow, and executing a run.

The Marbles model separates the description of work from a run of that work. The authoring API follows the same distinction: create a definition first, then decide how it will be used.

Declare a step

import { step } from "@foundry.sh/marbles";

const count = step("count-files").do(async ({ workspaces }) => {
  const files = await workspaces.current.files();
  return files.length;
});

step("count-files") creates a builder. .do(...) supplies the callback and returns a StepDefinition, held here by count. Loading the module registers that name in the catalog. The callback has not executed yet.

Other builder methods describe the work before .do(...): .input(schema) defines the values it accepts, .output(schema) describes its result, and .describe(text) explains its purpose. The next page uses these contracts to connect steps.

Place it in a workflow or launch it

The same definition can be used in several ways:

Expression What happens
count({}) Creates a node ready to place in a workflow. No work executes.
count({}, input) Creates a node carrying literal input, when the step declares an input schema.
await count.run() Executes the definition directly and returns its result.
marbles roll count-files Launches the registered definition through the CLI.

A node is one placement of a definition in a workflow tree. Its first argument describes the child steps whose results it needs; the second supplies literal input. Creating a node is called locking a definition: it fixes those children and inputs for that position.

A run begins when Marbles executes the definition or tree. Calling count({}) while describing a workflow does not start a run. This is what lets you compose work before launching it.

Use explicit, unique names for launchable steps and workflows. Shared helper definitions can live in imported modules; see the catalog rules for name inference and discovery.

Resource instances

A resource instance combines a declaration with methods that use it:

import { step, workspace } from "@foundry.sh/marbles";

const project = workspace({ path: "." });

step("read-project").do(async () => {
  const files = await project.files();
  return { files: files.length };
});

project is a WorkspaceInstance. Its definition identifies a directory; its files() method loads and reads that directory when called. The underlying file operations come from Workspaces.

The same distinction applies to other resources: an agent declaration supplies instructions, and its methods perform model work; a sandbox declaration supplies an environment, and its first operation starts it. The API reference lists the complete methods and types.

Keep module scope to declarations

Marbles imports automation modules even for list. A factory call or step builder describes what is available; an operation such as project.files() or an agent turn performs work. Put operations in step callbacks so they happen when you launch the automation.

Inside a step, resource methods use its execution context. That is how Marbles supplies their working directory and owns their activity. The Contexts page explains that connection after we have a workflow to run.

Next: Steps and workflows connects these nodes so one step can consume another’s result.

Was this page helpful?