Sessions and Prompts
How agent.session() and session.prompt() work, and what the harness loop does between the model and your tools.
A session is a persisted message and context thread, and inside one 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, and the framework will ask the model for a typed object and validate it against the schema before you see it.
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/sdk';
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. TechFabric 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, and 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.
Filesystem aliases follow familiar sandbox conventions: 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.