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

Agents and sessions

Add model work to a step, understand its bindings, and keep a conversation when needed.

An agent supplies instructions for model work. A session is a conversation with that agent. A turn is one request and reply within the conversation.

The workflow still decides when the step runs and what happens to its result. The agent supplies judgment inside that step, just as a command supplies a calculation or test result.

Open a session in a step

This complete module asks for one review. It requires an installed and authenticated Claude Code or Codex CLI; see agent setup.

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

const reviewer = agent({
  name: "readme-reviewer",
  prompt:
    "Review README.md for clarity. Report findings without editing files.",
});

step("review-readme").do(async ({ agents }) => {
  const session = await agents.session(reviewer);
  const reply = await session.generate(
    "Which instructions need clarification?"
  );
  return reply.text;
});

agent(...) declares instructions. agents.session(...) opens the conversation. session.generate(...) performs a turn, and the step returns its text as the result. Run it with marbles roll review-readme.

Agents provides the session and harness contracts. Models resolves the provider and model that perform the turn.

How the session joins the workflow

The execution context binds this session to its step. Its turns inherit the step’s working directory, write to its output stream, and stop when the step is cancelled or paused. Inside a Git worktree callback, a newly opened session uses that checkout. Pass { cwd } only when the host session should use another directory.

That binding is the connection between agent execution and the surrounding workflow. The session handles conversation; the step handles its input, result, progress, and execution policy. Return the findings so another step can use them instead of asking the model to infer which stage comes next.

A prompt asking the agent not to edit is an instruction, not a filesystem permission. See safety and limits for host and sandbox authority.

Session lifetime and provider

A fresh agents.session(reviewer) opens a fresh conversation during normal execution. Use the same returned session for follow-up turns. A session runs one turn at a time, so await one reply before sending the next request.

For a single turn, an agent instance also has generate and stream methods. Inside a step, await reviewer.generate("Review README.md.") opens its own fresh session and uses the same run bindings. Use an explicit session when you want several turns to share a conversation.

provider chooses who performs the turn, and model selects a model that provider serves. The standalone CLI detects claude and codex on PATH, prefers Claude Code when both are present, and accepts --harness to narrow the choice. Authentication belongs to those CLIs.

Continue the conversation across runs

A session object is useful inside one body. Its small, serializable session.ref identifies the conversation for another step or a later run. This separate module accepts an optional reference and returns the one it used. Install zod@4.4.3 in the automation project for its input schema.

import { agent, step } from "@foundry.sh/marbles";
import { z } from "zod";

const reviewer = agent({
  prompt:
    "Review this repository without editing. Compare with earlier findings.",
});

const sessionRef = z.object({
  id: z.string(),
  model: z.string().optional(),
  provider: z.string().optional(),
});

step("review-project")
  .input(z.object({ request: z.string(), session: sessionRef.optional() }))
  .do(async ({ input, agents }) => {
    const session = await agents.session(
      reviewer,
      input.session ? { session: input.session } : {}
    );
    const reply = await session.generate(input.request);
    return { text: reply.text, session: session.ref };
  });
marbles roll review-project --input '{"request":"Review the repository and remember unresolved findings."}'
marbles roll review-project --input '{"request":"Compare this revision with your earlier findings.","session":{"id":"<id from the first result>","provider":"claude-code"}}'
marbles sessions

Copy the returned session reference into the second command, including its provider. Use codex if that is what the first run returned. A reference belongs to the provider that opened it; continuing it with a different provider starts a new session and logs a warning. marbles sessions lists saved conversations with their message counts.

Continuing a conversation does not resume the earlier workflow run. This is a new run using retained messages. The factory reviewer saves a reference per issue so later revisions continue its review history.

Structured output

Pass an AI SDK Output to a turn and the reply carries the parsed value:

import { agent, Output, step } from "@foundry.sh/marbles";
import { z } from "zod";

const triage = agent({ prompt: "Triage issues for this repository." });
const verdict = Output.object({
  schema: z.object({
    labels: z.array(z.string()),
    severity: z.enum(["low", "medium", "high"]),
  }),
});

step("triage")
  .input(z.object({ issue: z.string() }))
  .do(async ({ input, agents }) => {
    const session = await agents.session(triage);
    const { output } = await session.generate(input.issue, { output: verdict });
    return output; // { labels: string[]; severity: "low" | "medium" | "high" }
  });

Claude Code and Codex receive the schema, and Marbles validates their reply. A mismatch rejects with NoObjectGeneratedError; the reply still remains in the transcript. A valid parsed value is stored with the reply.

Output.array, Output.choice, and Output.json work the same way. @foundry.sh/marbles re-exports Output from the AI SDK. For streaming, turn settings, and compaction, see Agent sessions.

Skills and sandbox sessions

Skills supply reusable instructions and files to an agent. skills.load() collects them from ~/.foundry/skills and the project’s skills/ folder; .add(glob) adds directories and .pick(...names) narrows the set. Pass that set as the agent’s skills.

Host sessions work in a local directory. agents.session(def, { sandbox: box }) opens the conversation in the sandbox introduced on the previous page; its mounts determine the working directory, so do not also supply cwd.

The session reference covers permission profiles, compaction, child agents, and additional session settings. The prebuilt steps provide reviewers with those sessions already wired.

A conversation records messages; a completed step records the result used by its workflow. Durability later explains how replay and retry affect both, including when a turn can execute again.

Next: Artifacts and results explains how to pass findings to another step or keep them as a versioned report.

Was this page helpful?