MySQL
Add least-privilege, parameterized MySQL tools.
Runtime persistence
pnpm add @fabric-harness/node mysql2import { mysqlPersistence } from '@fabric-harness/node';
import mysql from 'mysql2/promise';
export default function config() {
const pool = mysql.createPool(process.env.MYSQL_URL!);
return { persistence: mysqlPersistence({ client: pool }) };
}mysqlPersistence() supplies sessions, submissions, conversation streams, binary attachments,
runs/events, atomic cost budgets, health, migration, and cascade deletion. It stores one
version-fenced JSON snapshot per persistence surface and uses compare-and-swap updates, so multiple
replicas cannot both commit the same transition.
Use MySQL 8/InnoDB, TLS, backups, and a max_allowed_packet larger than your largest attachment
snapshot. Snapshot rows favor moderate agent operational state; use Postgres when workloads have
very high write concurrency or histories large enough that rewriting a surface snapshot is costly.
Governed data tools
Use the fixed-statement MySQL tool. Scaffold mysql2 and
@fabric-harness/databases/mysql with:
fh add database mysqlconst lookup = mysqlTool({
client: pool,
name: 'lookup_account',
description: 'Read one tenant-scoped account.',
statement: 'select id, status from accounts where tenant_id = ? and id = ? limit 1',
parameters: ({ tenantId, accountId }) => [tenantId, accountId],
});The application owns the SQL; model input only becomes bound parameters. Read tools reject
mutations and multi-statements. Use a least-privilege user, keep multipleStatements: false, and
mark controlled mutations effect: 'write' so approvals can gate them.
Runtime credentials stay in host configuration and are never added to model-facing tools.