Sessions and Prompts
agent.session(), session.prompt(), and the harness loop.
A session is a persisted message/context thread. Inside a session, you can run prompts, skills, tasks, and shell commands.
Create a session
const fabricAgent = await init();
const session = await fabricAgent.session(); // new id
const resumed = await fabricAgent.session('s-001'); // resume by id
const scoped = await fabricAgent.session('s-002', {
role: 'engineer',
model: 'openai/gpt-5.5',
cwd: 'project',
});session.prompt(text, options?)
Run one harness loop turn — a single user prompt that may produce assistant messages, tool calls, and shell commands until the model returns a final answer.
const answer = await session.prompt('What is Temporal?');Typed results
Use result to validate and type the return value. The framework asks the model for a typed object and validates it against the schema.
schemais also exported from@fabric-harness/sdk— same API in both.
import { schema } from '@fabric-harness/sdk';
const triage = await session.prompt('Triage this issue', {
result: schema.object({
severity: schema.enum(['low', 'medium', 'high', 'critical']),
summary: schema.string(),
recommendedLabels: schema.array(schema.string()),
}),
});Streaming
for await (const event of session.stream('Tell me about Temporal')) {
if (event.type === 'text_delta') process.stdout.write(event.text);
}Working directories
Set cwd at agent, session, prompt/skill/task, or shell scope. Relative cwd values are resolved inside the sandbox and cannot escape the sandbox workspace.
const fabricAgent = await init({ cwd: 'project' });
const session = await fabricAgent.session();
await session.shell('npm install'); // runs in /workspace/project
await session.shell('npm test', { cwd: 'ui' }); // runs in /workspace/project/ui
await session.prompt('Inspect the UI package', { cwd: 'ui' });
await session.skill('review', { cwd: 'api', args: { focus: 'routes' } });
await session.task('Refactor tests', { cwd: 'packages/core' });File tools (read, write, grep, glob) use the same scoped cwd during prompts/skills/tasks, so coding agents can work in a repository subdirectory without repeating absolute paths.
Tools and commands
import { defineCommand } from '@fabric-harness/node';
const npm = defineCommand('npm');
const git = defineCommand('git');
await session.prompt('Run the failing tests and propose a fix', {
commands: [npm, git],
// tools: optional override of built-in tools
});Reasoning streams
Reasoning-capable models (Anthropic Claude with extended thinking, OpenAI o-series, Gemini 2.5 with thought summaries) emit a separate "thinking" content channel. Fabric Harness surfaces it on ModelResponse.thinking and emits a text_delta event with kind: 'thinking' so streaming consumers can render it apart from the final answer.
const stream = session.stream('Plan the migration in detail.');
for await (const event of stream) {
if (event.type === 'text_delta' && event.data?.kind === 'thinking') {
renderThinking(event.data.delta); // grey/italic in your UI
} else if (event.type === 'text_delta') {
renderOutput(event.data?.delta); // normal model output
}
}Provider notes:
- Anthropic: thinking is emitted when the request includes
extended_thinking: true(set viaheadersonAnthropicModelProvider). - OpenAI o-series: emitted automatically as
reasoning_contenton the chat completion message. - Gemini 2.5: emitted when
thinking_configis set on the request; consumed via the samethinkingfield.
Other providers leave ModelResponse.thinking undefined; consumers should fall back to text_delta without the thinking kind.
Token-level streaming
OpenAICompatibleModelProvider and AnthropicModelProvider both expose a stream() method that returns an AsyncIterable<ModelStreamChunk>. The loop uses it automatically when present, emitting text_delta events as tokens arrive (instead of buffering until the response completes). UIs see word-by-word output; per-call cost telemetry still lands on the final aggregated chunk.
Consumers don't need code changes — session.stream() already understands text_delta:
for await (const event of session.stream(prompt)) {
if (event.type === 'text_delta') process.stdout.write(event.data?.delta ?? '');
}Mid-stream failures fall through to the loop's normal error path. Retries are NOT automatic for streamed calls — partial state can't be safely re-applied.
session.skill(name, options?)
Invoke a Markdown skill from .fabricharness/skills/<name>/SKILL.md. Args interpolate into the skill body, and result validates the typed return value.
const triage = await session.skill('triage-issue', {
args: { issueNumber: 42, repository: 'octocat/repo' },
commands: [gh],
result: schema.object({
severity: schema.enum(['low', 'medium', 'high', 'critical']),
}),
});session.task(text, options?)
Spawn a child task. Tasks are durable when the session runs on the Temporal worker target.
const result = await session.task('Refactor the authentication middleware', {
id: 'refactor-auth',
result: schema.object({ filesChanged: schema.array(schema.string()) }),
});session.shell(command, options?)
Execute a shell command in the session's sandbox.
const out = await session.shell('npm test', { cwd: '/workspace' });session.fs and agent.fs
Use session.fs for host-side filesystem plumbing that should not appear in model history: staging input files, collecting scratch output, or checking whether generated artifacts exist. It uses the same sandbox backend and cwd as the session.
const fabric = await init({ sandbox: 'virtual' });
const session = await fabric.session('build-123');
await session.fs.writeText('input/ticket.md', ticketBody);
const exists = await session.fs.exists('input/ticket.md');
const files = await session.fs.list('input');
const text = await session.fs.readText('input/ticket.md');agent.fs is also available for quick setup scripts. It is backed by a lazily-created default session; prefer session.fs when you need explicit session identity, audit correlation, or lifecycle control.
Aliases are provided for Flue parity and sandbox familiarity: readFile, readFileBuffer, writeFile, readdir, and rm.
Session history
const history = await session.history();
// { id, createdAt, updatedAt, entries, events }The CLI's fh inspect, fh logs, and fh metrics are wrappers around this same data.