Deep Agents
AgentContextOrchestratorRetrievalText2SQLToolbox

History

Persist Text2SQL conversations through ContextEngine stores

Text2SQL no longer exposes a separate History class. Conversation persistence comes from the ContextStore backing the ContextEngine you wire into the chat() helper from @deepagents/context. That helper writes turns to the store; toSql() does not persist conversation history.

Choose a Store

Use in-memory storage for tests and short-lived processes:

import { groq } from '@ai-sdk/groq';

import {
  ContextEngine,
  InMemoryContextStore,
  agent,
  createBashTool,
  createDockerSandbox,
  errorRecoveryGuardrail,
  npm,
  user,
} from '@deepagents/context';
import { createSqlCommandHooks, instructions } from '@deepagents/text2sql';

const model = groq('openai/gpt-oss-20b');

const store = new InMemoryContextStore();
const backend = await createDockerSandbox({
  installers: [npm('@deepagents/text2sql', { ensureRuntime: true })],
  env: {
    TEXT2SQL_ADAPTERS: '/workspace/text2sql-adapters.ts',
  },
});
const sandbox = await createBashTool({
  sandbox: backend,
  ...createSqlCommandHooks({ adapters }),
});
const context = new ContextEngine({
  store,
  chatId: 'chat-123',
  userId: 'user-456',
});
const indexResult = await sandbox.sandbox.executeCommand('sql index');
if (indexResult.exitCode !== 0) throw new Error(indexResult.stderr);
const manifest = JSON.parse(indexResult.stdout) as { fragmentsPath: string };
const fragments = JSON.parse(
  await sandbox.sandbox.readFile(manifest.fragmentsPath),
);
context.set(...instructions(), ...fragments);

const ai = agent({
  name: 'sql-assistant',
  sandbox,
  model,
  context,
  guardrails: [errorRecoveryGuardrail],
  maxGuardrailRetries: 3,
});

Use SQLite when you want persistence across server restarts:

import { groq } from '@ai-sdk/groq';

import {
  ContextEngine,
  SqliteContextStore,
  agent,
  createBashTool,
  createDockerSandbox,
  errorRecoveryGuardrail,
  npm,
  user,
} from '@deepagents/context';
import { createSqlCommandHooks, instructions } from '@deepagents/text2sql';

const model = groq('openai/gpt-oss-20b');

const store = new SqliteContextStore('./text2sql-history.sqlite');
const backend = await createDockerSandbox({
  installers: [npm('@deepagents/text2sql', { ensureRuntime: true })],
  env: {
    TEXT2SQL_ADAPTERS: '/workspace/text2sql-adapters.ts',
  },
});
const sandbox = await createBashTool({
  sandbox: backend,
  ...createSqlCommandHooks({ adapters }),
});
const context = new ContextEngine({
  store,
  chatId: 'chat-123',
  userId: 'user-456',
});
const indexResult = await sandbox.sandbox.executeCommand('sql index');
if (indexResult.exitCode !== 0) throw new Error(indexResult.stderr);
const manifest = JSON.parse(indexResult.stdout) as { fragmentsPath: string };
const fragments = JSON.parse(
  await sandbox.sandbox.readFile(manifest.fragmentsPath),
);
context.set(...instructions(), ...fragments);

const ai = agent({
  name: 'sql-assistant',
  sandbox,
  model,
  context,
  guardrails: [errorRecoveryGuardrail],
  maxGuardrailRetries: 3,
});

Both history examples assume adapters is the host-side adapter map and that /workspace/text2sql-adapters.ts exists inside the sandbox with the same map as its default export. Mount, upload, or write that module before calling chat().

Continuing a Conversation

Reuse the same store, chatId, and userId for follow-up turns. In practice, that usually means reusing the same agent instance or creating a new one with a new ContextEngine built from the same thread identity. Pass only the new incoming message for that turn; earlier turns are loaded from the store automatically:

import { chat } from '@deepagents/context';

await context.continue(user('Show me sales data'));
for await (const _ of await chat(ai, { generateTitle: true })) {
  // drain or stream chunks to your UI
}

await context.continue(user('Filter that to last quarter'));
for await (const _ of await chat(ai)) {
  // drain or stream chunks to your UI
}

Create a new chatId when you want to start a separate thread that should not reuse prior context.

What Gets Persisted

Persistence is owned by the chat() helper from @deepagents/context, not by Text2Sql. When you call chat(ai), that helper writes through the underlying ContextEngine:

  • the incoming user or assistant message for that turn (queued by context.continue(...))
  • the finalized assistant reply for the turn
  • chat-level data such as the generated title and tracked usage

That gives later chat() calls access to prior turns without a separate history API on Text2Sql itself. Direct toSql() calls skip the store entirely.

Direct Store Access

If you need to inspect or manage persisted chats directly, work with the @deepagents/context storage layer rather than expecting Text2Sql helper classes.

See:

Best Practices

  1. Share one store across requests when you want continuity across server restarts.
  2. Keep chatId stable inside a thread so follow-up questions resolve prior turns.
  3. Use a fresh chatId for a new task instead of mixing unrelated analyses together.
  4. Prefer SqliteContextStore in production when process memory alone is not durable enough.