SQL databases in Rust, designed to be intuitive and type-safe.
A Rust port of tiangolo/sqlmodel (Python), extended with asupersync for structured concurrency and cancel-correct async database operations.
The Problem: Existing Rust ORMs are either too low-level (raw SQL strings), too magical (runtime reflection), or force you to learn complex DSLs. You shouldn't need a PhD in database theory to insert a row.
The Solution: SQLModel Rust provides Python SQLModel's developer experience with Rust's compile-time safety. Define your models with derive macros, query with type-safe builders, and let the compiler catch your mistakes.
| Feature | What It Does |
|---|---|
| Zero-cost derive macros | #[derive(Model)] generates efficient code at compile time—no runtime reflection |
| Type-safe query builder | Compile-time validation of SQL expressions, columns, and joins |
| Cancel-correct async | Built on asupersync for structured concurrency |
| Multi-dialect support | Single codebase generates Postgres, SQLite, or MySQL SQL |
| Lean dependencies | No tokio/sqlx/diesel/sea-orm; core stays small, drivers/validation use focused crypto/regex deps |
use sqlmodel::prelude::*;
#[derive(Model, Debug)]
#[sqlmodel(table = "heroes")]
struct Hero {
#[sqlmodel(primary_key, auto_increment)]
id: Option<i64>,
#[sqlmodel(unique)]
name: String,
secret_name: String,
#[sqlmodel(nullable)]
age: Option<i32>,
#[sqlmodel(foreign_key = "teams.id")]
team_id: Option<i64>,
}
// Type-safe query building; the SQL can be rendered for any dialect without a connection
let query = select!(Hero)
.filter(Expr::col("age").gt(18))
.order_by(Expr::col("name").asc())
.limit(10);
let (sql, params) = query.build_with_dialect(Dialect::Postgres);
assert_eq!(
sql,
"SELECT * FROM \"heroes\" WHERE \"age\" > $1 ORDER BY \"name\" ASC LIMIT 10"
);
assert_eq!(params, vec![Value::Int(18)]);
// Execute against a connection: every operation takes a `Cx` and returns an
// `Outcome` (Ok / Err / Cancelled / Panicked)
async fn adult_heroes(cx: &Cx, conn: &impl Connection) -> Outcome<Vec<Hero>, Error> {
select!(Hero)
.filter(Expr::col("age").gt(18))
.order_by(Expr::col("name").asc())
.limit(10)
.all(cx, conn)
.await
}With the nightly-try feature (this workspace pins a nightly toolchain anyway), ? works on
Outcome and propagates errors and cancellation alike; the examples below use it. Without the
feature, match on the four variants.
We extracted the behavior specification from Python SQLModel/SQLAlchemy/Pydantic, then implemented fresh in Rust. No line-by-line translation. Rust has compile-time types and macros—we use them instead of runtime validation.
All Model implementations are generated at compile time via proc macros. No runtime reflection, no vtables. Measured against hand-written equivalents
(13-column model, criterion, RCH worker hz3, 2026-09-06): to_row runs ≈197 ns
vs 150 ns hand-written; SQL emission costs ≈0.15–2.8 µs per statement by shape.
The one measured gap: generated from_row resolves columns in a single-pass scan
(Row::locate_columns, bd-n5q4) and runs ≈491 ns vs 206 ns hand-written (down from 1.43 µs).
Benchmarks live in crates/sqlmodel-e2e/benches/, with CI thresholds in
benches/thresholds.toml.
Every async operation takes &Cx (capability context) and returns Outcome<T, E> instead of Result. This enables:
- Cancel-correct operations (no leaked resources)
- Budget/timeout enforcement
- Proper panic boundaries
The query builder validates at compile time when possible, and provides clear error messages when runtime checks are needed. We'd rather fail at compile time than corrupt your database.
Core crates keep dependencies tight:
asupersync- Async runtime with structured concurrencyserde/serde_json- Serializationproc-macro2/quote/syn- Macro support
Drivers and validation add focused dependencies where required (e.g., TLS/auth crypto, regex validation), but we still avoid heavyweight ORM/database stacks.
No tokio, no sqlx, no diesel, no sea-orm. We build what we need.
| Feature | SQLModel Rust | Diesel | SeaORM | sqlx |
|---|---|---|---|---|
| Compile-time safety | ✅ Full | ✅ Full | ||
| Derive macros | ✅ Simple | ✅ Simple | ❌ None | |
| Structured concurrency | ✅ Native | ❌ None | ❌ None | ❌ None |
| Multi-dialect | ✅ Postgres/SQLite/MySQL | ✅ Yes | ✅ Yes | |
| Dependencies | ✅ Minimal | 🐢 Heavy | 🐢 Heavy | |
| Learning curve | ✅ Low | ❌ Steep | ✅ Low |
When to use SQLModel Rust:
- You want Python SQLModel's ergonomics in Rust
- You need cancel-correct async with structured concurrency
- You prefer compile-time errors over runtime surprises
- You're building from scratch and want minimal dependencies
When SQLModel Rust might not fit:
- You need an established ecosystem with extensive documentation
- You require immediate production readiness (we're in active development)
- You need implicit relationship traversal/lazy loading without explicit load calls (we provide
Lazy<T>+Session::load_lazy/load_many, but we avoid hidden N+1 behavior)
Version 0.5.1 is available on crates.io for all 12 SQLModel crates.
# Cargo.toml
[dependencies]
sqlmodel = "0.5.1"
# `#[derive(Model)]` expands to `sqlmodel_core::` paths, so a crate that derives
# models also needs the core crate directly, at the same version (issue #37):
sqlmodel-core = "0.5.1"
# Choose a driver (pick one or more)
sqlmodel-postgres = "0.5.1"
# sqlmodel-mysql = "0.5.1"
# sqlmodel-sqlite = "0.5.1"
# sqlmodel-frankensqlite = "0.5.1"
# Optional rich console output
sqlmodel-console = { version = "0.5.1", features = ["rich"] }
# TLS-only MySQL deployments can drop the `rsa` dependency (the no-TLS
# full-auth path is compiled out; the server must then allow TLS auth):
# sqlmodel-mysql = { version = "0.5.1", default-features = false, features = ["tls"] }All SQLModel crates are released in lockstep at the same version (0.5.1); mix and match drivers within that release family.
You do not need to add asupersync directly; the Cx and Outcome types are
re-exported from sqlmodel and sqlmodel-core.
git clone https://github.com/Dicklesworthstone/sqlmodel_rust.git
cd sqlmodel_rust
# Build the workspace
cargo build --workspace
# Run tests
cargo test --workspaceOne complete program (this block is compiled as a doctest, so it cannot drift from the API):
use sqlmodel::prelude::*;
use sqlmodel::SchemaBuilder;
#[derive(Model, Debug, Clone)]
#[sqlmodel(table = "users")]
struct User {
#[sqlmodel(primary_key, auto_increment)]
id: Option<i64>,
#[sqlmodel(unique)]
email: String,
name: String,
#[sqlmodel(default = "false")]
is_active: bool,
}
/// DDL for the connection's own dialect. The builder defaults to SQLite; pass
/// `conn.dialect()` for anything else (identifier quoting, column types, and the
/// auto-increment key form differ per engine).
fn schema_for(conn: &impl Connection) -> Vec<String> {
SchemaBuilder::new()
.dialect(conn.dialect())
.create_table::<User>()
.build()
}
async fn crud(cx: &Cx, conn: &impl Connection) -> Outcome<(), Error> {
for statement in schema_for(conn) {
conn.execute(cx, &statement, &[]).await?;
}
// INSERT (returns the new id; PostgreSQL gets an automatic RETURNING clause)
let alice = User {
id: None,
email: "alice@example.com".into(),
name: "Alice".into(),
is_active: true,
};
let id = insert!(&alice).execute(cx, conn).await?;
// SELECT
let active: Vec<User> = select!(User)
.filter(Expr::col("is_active").eq(true))
.order_by(Expr::col("name").asc())
.all(cx, conn)
.await?;
// UPDATE by primary key, from a model instance
let mut alice = active.into_iter().next().expect("inserted above");
alice.name = "Alice Smith".into();
let updated = update!(&alice).execute(cx, conn).await?;
// DELETE by filter
let deleted = delete!(User)
.filter(Expr::col("is_active").eq(false))
.execute(cx, conn)
.await?;
println!("inserted {id}, updated {updated}, deleted {deleted}");
Outcome::Ok(())
}The SQLite DDL schema_for produces:
CREATE TABLE IF NOT EXISTS "users" (
"id" INTEGER PRIMARY KEY,
"email" TEXT NOT NULL,
"name" TEXT NOT NULL,
"is_active" BOOLEAN NOT NULL DEFAULT false,
CONSTRAINT "uk_users_email" UNIQUE ("email")
)The SQLite "id" INTEGER PRIMARY KEY above is the auto-assigning rowid alias; PostgreSQL gets
"id" BIGINT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY and MySQL
`id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY. Introspection reports all three back as
auto_increment, so an auto-increment model is a fixpoint of the schema differ on every engine
(proven by the e2e attributes scenario). Column types are adapted per engine
(BYTEA for byte fields on PostgreSQL; BINARY(16) for UUIDs and VARCHAR(255) for unique,
indexed, or key String columns on MySQL, which cannot index TEXT; and so on).
Every driver starts transactions through the same Connection API. TransactionOptions adds a
TransactionMode on top of the isolation level: Concurrent selects FrankenSQLite's BEGIN CONCURRENT
(page-level MVCC, several writers on one file) and is the native behavior on PostgreSQL and MySQL;
Immediate, Exclusive, and Deferred pick SQLite's locking forms. A driver that cannot honor a mode
returns TransactionErrorKind::UnsupportedMode instead of silently downgrading (C SQLite has no
concurrent-writer mode). Conflicting concurrent writers fail with a retryable error, and
retry_transaction re-runs the whole transaction with jittered backoff that respects the Cx budget
and never retries after a cancellation:
use sqlmodel::prelude::*;
use sqlmodel::{Session, SessionConfig, TransactionMode, TransactionOptions};
async fn transfer<C: Connection>(cx: &Cx, conn: C) -> Outcome<i64, Error> {
let moved = retry_transaction(
cx,
&conn,
TransactionOptions::concurrent(),
&RetryPolicy::default(),
async |cx, tx| {
tx.execute(cx, "UPDATE accounts SET balance = balance - 10 WHERE id = ?1", &[Value::BigInt(1)]).await?;
tx.execute(cx, "UPDATE accounts SET balance = balance + 10 WHERE id = ?1", &[Value::BigInt(2)]).await?;
Outcome::Ok(10)
},
)
.await?;
// Sessions can start every transaction concurrently, and `Session::with_retry`
// re-runs a whole unit of work (rolling back and clearing the identity map
// between attempts) when a flush or commit hits a retryable conflict:
let mut session = Session::with_config(
conn,
SessionConfig::default().with_transaction_mode(TransactionMode::Concurrent),
);
let _ = session
.with_retry(cx, &RetryPolicy::default(), async |cx, session| {
session.connection().execute(cx, "UPDATE accounts SET audited = 1", &[]).await
})
.await?;
Outcome::Ok(moved)
}SQLModel Rust includes an optional rich console output system for beautiful terminal feedback.
- Styled error messages with context, SQL highlighting, and suggestions
- Formatted query result tables with type-based coloring
- Schema visualization as interactive trees
- Progress bars for bulk operations
- Agent-safe: auto-detects AI coding tools (Claude Code, Codex, Cursor, Aider, etc.)
Add the console feature to your dependency:
[dependencies]
sqlmodel-console = { version = "0.5.1", features = ["rich"] }Create and use a console:
// (requires the `console` feature)
use sqlmodel_console::{SqlModelConsole, OutputMode};
use sqlmodel_console::renderables::QueryResultTable;
// Auto-detect mode (rich for humans, plain for agents)
let console = SqlModelConsole::new();
// Display query results
let table = QueryResultTable::new()
.columns(vec!["id", "name", "email"])
.row(vec!["1", "Alice", "alice@example.com"])
.timing_ms(12.34);
console.print_table(&table);| Mode | When Used | Output |
|---|---|---|
| Rich | Human on TTY | Colors, tables, panels |
| Plain | Agent detected / piped | Parseable text |
| JSON | SQLMODEL_JSON=1 |
Structured JSON |
Console output is agent-safe by default. When running under Claude Code, Codex CLI, Cursor, or other AI coding tools, output automatically switches to plain text that agents can parse.
Environment variables for control:
SQLMODEL_PLAIN=1- Force plain text modeSQLMODEL_RICH=1- Force rich mode (even for agents)SQLMODEL_JSON=1- Force JSON output
- ORM User Guide - Comprehensive 11-chapter guide (models, queries, sessions, relationships, inheritance, migrations, drivers, pooling, errors, cancellation, testing)
- Console User Guide - Complete feature guide
- Agent Compatibility Guide - For agent authors
- Proposed Rust Architecture - Crate boundaries, invariants, and inheritance/query design
- Existing SQLModel Structure - Behavior specification extracted from legacy Python projects
- Feature Parity Tracker - Status of parity work against Python SQLModel
Run the example programs to preview both rich and plain output:
cargo run -p sqlmodel-console --example console_demo
cargo run -p sqlmodel-console --example error_showcase
cargo run -p sqlmodel-console --example query_results
cargo run -p sqlmodel-console --example progress_demo
cargo run -p sqlmodel-console --example schema_visualization┌─────────────────────────────────────────────────────────────────┐
│ sqlmodel (facade) │
│ Re-exports all crates for easy import │
└─────────────────────────────────────────────────────────────────┘
│
┌───────────────┬───────────────┬───────────────┬───────────────┬───────────────┐
▼ ▼ ▼ ▼ ▼
┌─────────────┐ ┌───────────────────┐ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│sqlmodel-core│ │ sqlmodel-macros │ │ sqlmodel-query │ │ sqlmodel-schema │ │ sqlmodel-session│
│ Model trait │ │ #[derive(Model)] │ │ Query builder │ │ DDL + migration │ │ Unit of work │
└─────────────┘ └───────────────────┘ └─────────────────┘ └─────────────────┘ └─────────────────┘
│
┌─────────────┴─────────────┐
▼ ▼
┌─────────────┐ ┌─────────────────┐
│sqlmodel-pool│ │sqlmodel-console │ (optional)
│Conn pooling │ │Rich output │
└─────────────┘ └─────────────────┘
│
┌─────────┼─────────┬─────────┬──────────────────┐
▼ ▼ ▼ ▼ ▼
sqlmodel-postgres sqlmodel-mysql sqlmodel-sqlite sqlmodel-frankensqlite (drivers)
(C SQLite, FFI) (pure Rust, MVCC; nightly)
| Crate | Purpose |
|---|---|
sqlmodel |
Facade crate—re-exports everything for use sqlmodel::prelude::* |
sqlmodel-core |
Core traits (Model, Connection), types (Value, Row, Error) |
sqlmodel-macros |
#[derive(Model)] proc macro with attribute parsing and code gen |
sqlmodel-query |
Type-safe query builder with multi-dialect support |
sqlmodel-schema |
DDL generation, schema builder, migration support |
sqlmodel-session |
Unit of work + identity map |
sqlmodel-pool |
Connection pooling with asupersync channels |
sqlmodel-e2e |
All-driver end-to-end scenarios, oracles, and golden SQL (not published) |
sqlmodel-postgres |
PostgreSQL wire protocol implementation |
sqlmodel-mysql |
MySQL wire protocol implementation |
sqlmodel-sqlite |
SQLite driver (C SQLite via bundled libsqlite3-sys) |
sqlmodel-frankensqlite |
Pure-Rust SQLite driver (FrankenSQLite, page-level MVCC, BEGIN CONCURRENT); nightly-only |
sqlmodel-console |
Optional rich console output for humans and agents |
// (attribute cheat-sheet, not a compilable model)
#[derive(Model)]
#[sqlmodel(table = "custom_table_name")] // Override table name
struct MyModel {
#[sqlmodel(primary_key)] // Part of primary key
#[sqlmodel(auto_increment)] // Auto-increment (usually with primary_key)
#[sqlmodel(unique)] // UNIQUE constraint
#[sqlmodel(nullable)] // Allow NULL values
#[sqlmodel(column = "db_column")] // Override column name
#[sqlmodel(sql_type = "VARCHAR(100)")]// Override SQL type
#[sqlmodel(default = "value")] // DEFAULT clause
#[sqlmodel(foreign_key = "table.col")]// FOREIGN KEY constraint
#[sqlmodel(index)] // Create index on column
#[sqlmodel(skip)] // Exclude from all DB operations
field: Type,
}The wire format is dependency-free: dates are days since 1970-01-01, times and timestamps are
i64 microseconds, UUIDs are 16 bytes, decimals are text (Value::Date, Value::Time,
Value::Timestamp, Value::TimestampTz, Value::Uuid, Value::Decimal). The external types in
the last rows are opt-in features on sqlmodel / sqlmodel-core; without the feature a model field
of that type does not compile.
| Rust Type | SQL Type | Feature |
|---|---|---|
i8 |
TINYINT |
— |
i16 |
SMALLINT |
— |
i32 |
INTEGER |
— |
i64 |
BIGINT |
— |
f32 |
REAL |
— |
f64 |
DOUBLE PRECISION |
— |
bool |
BOOLEAN |
— |
String |
TEXT |
— |
char |
CHAR(1) |
— |
Option<T> |
Nullable version of T | — |
Vec<u8> |
BYTEA / BLOB |
— |
[u8; 16] |
UUID |
— |
serde_json::Value |
JSON / JSONB |
— |
chrono::NaiveDate |
DATE |
chrono |
chrono::NaiveTime |
TIME |
chrono |
chrono::NaiveDateTime |
TIMESTAMP (stored as UTC microseconds) |
chrono |
chrono::DateTime<Utc> / DateTime<FixedOffset> |
TIMESTAMPTZ (offset applied, not stored) |
chrono |
uuid::Uuid |
UUID (BINARY(16) on MySQL, 16-byte BLOB on SQLite) |
uuid |
rust_decimal::Decimal |
NUMERIC / DECIMAL (scale preserved; TEXT on SQLite so digits stay exact) |
decimal |
# `nightly-try` enables `?` on `Outcome` (nightly toolchain, see above)
sqlmodel = { version = "0.5.1", features = ["chrono", "uuid", "decimal", "nightly-try"] }// (expression catalogue; each line is an `Expr`, the comment shows its SQL)
use sqlmodel::Expr;
// Column references
Expr::col("name") // "name"
Expr::qualified("users", "name") // "users"."name"
// Comparisons
Expr::col("age").eq(18) // "age" = $1
Expr::col("age").ne(18) // "age" != $1
Expr::col("age").gt(18) // "age" > $1
Expr::col("age").ge(18) // "age" >= $1
Expr::col("age").lt(18) // "age" < $1
Expr::col("age").le(18) // "age" <= $1
// Null checks
Expr::col("deleted").is_null() // "deleted" IS NULL
Expr::col("name").is_not_null() // "name" IS NOT NULL
// Pattern matching
Expr::col("name").like("%john%") // "name" LIKE $1
Expr::col("email").ilike("%@GMAIL%") // "email" ILIKE $1 (Postgres)
// Lists and ranges
Expr::col("status").in_list([1, 2, 3]) // "status" IN ($1, $2, $3)
Expr::col("age").between(18, 65) // "age" BETWEEN $1 AND $2
// Logical operators
expr1.and(expr2) // (expr1) AND (expr2)
expr1.or(expr2) // (expr1) OR (expr2)
Expr::not(expr) // NOT (expr)
// Aggregates
Expr::count_star() // COUNT(*)
Expr::col("id").count() // COUNT("id")
Expr::col("amount").sum() // SUM("amount")
Expr::col("price").avg() // AVG("price")
Expr::col("age").min() // MIN("age")
Expr::col("age").max() // MAX("age")
// CASE expressions
Expr::case()
.when(Expr::col("status").eq("active"), "Yes")
.when(Expr::col("status").eq("pending"), "Maybe")
.otherwise("No")| Capability | Status | Notes |
|---|---|---|
| Query execution | ✅ Complete | SELECT/INSERT/UPDATE/DELETE, upserts (on_conflict_*), bulk inserts (insert_many!), RETURNING (re-read by key on MySQL, which has none), the Join builder, typed subqueries (Expr::in_query, into_exists), and one-level eager loading (all_eager), each executed on every driver by the e2e operations scenario |
| Connection pooling | ✅ Complete | Generic pool with timeouts, health checks, metrics; proven on real connections (fan-out under one runtime, server-side counts, kills, panics, drains) |
| Prepared statements | ✅ Per driver | MySQL: every parameterized query/execute is prepared once per connection (64-entry cache, binary protocol) and executed per call. PostgreSQL: every parameterized query/execute is prepared and cached per connection (64-entry LRU cache, keyed by SQL + parameter OIDs; Close on eviction; unnamed statement for parameterless queries). SQLite drivers: no statement cache. Read from Com_stmt_* / pg_prepared_statements by the e2e session scenario; sqlmodel_query::cache::StatementCache only caches SQL text |
| Transactions | ✅ Complete | BEGIN/COMMIT/ROLLBACK with savepoint support |
| SQLite driver | ✅ Complete | Full Connection trait with transactions |
| MySQL driver | ✅ Complete | Wire protocol + SharedMySqlConnection |
| PostgreSQL driver | ✅ Complete | Wire protocol + SharedPgConnection with SCRAM auth |
| Runtime migrations | ✅ Complete | Schema diffing, migration runner (one transaction per migration on PostgreSQL and SQLite), version tracking with checksum drift detection |
| Lazy loading | ✅ Explicit | Lazy<T> + Session::load_lazy/load_many (batch-friendly; no implicit N+1). Opt-in detector: Session::enable_n1_detection(threshold) counts per-relationship loads, n1_stats() reports them |
- Rust 1.95+ for the facade and the Postgres/MySQL/SQLite drivers; nightly for
sqlmodel-frankensqlite(fsqlite enablescore_intrinsicson x86_64) and for building this repo itself (.cargo/config.tomlpasses-Z threads) - No stable release yet: API may change
- asupersync dependency: Resolved from crates.io as
^0.5.0, matching the runtime used byfsqlite0.4.x, so a single runtime version unifies across the stack - PostgreSQL runtime handoff (#43):
published SQLModel 0.5.1 can hang while validating a connection after its
holder's current-thread runtime exits on Asupersync 0.5.0. The issue's
explicit
worker_threads(2)controls pass. The repository now bounds checkout validation, but fixed-runtime integration and release qualification remain open. See the production qualification runner for the staged Asupersync 0.6.1 candidate and the published controls.
# SQLModel 0.5.x requires asupersync ^0.5.0, the same runtime line fsqlite 0.4.x
# uses, so one asupersync should resolve for the whole graph. If resolution
# fails, another dependency may require an older, incompatible runtime line:
cargo tree -i asupersync # find who requires the conflicting version
# then upgrade that crate (or use `cargo update -p asupersync` once it allows 0.5.0)
cargo buildOnly two things in this repository need nightly: sqlmodel-frankensqlite (its fsqlite-pager
dependency enables core_intrinsics on x86_64) and the workspace's own .cargo/config.toml, which
passes -Z threads=4 to rustc. Building the repository uses the pinned toolchain automatically:
rustup show # installs nightly-2026-08-25 from rust-toolchain.toml on first useConsumers of the published crates on stable Rust 1.95+ are unaffected unless they depend on
sqlmodel-frankensqlite.
The workspace has unsafe_code = "warn" by default. If you need unsafe code (e.g., for FFI), use #[allow(unsafe_code)] locally.
# Use sccache for faster rebuilds
cargo install sccache
export RUSTC_WRAPPER=sccache
cargo buildWe wanted Python SQLModel's simplicity with Rust's safety. Diesel is powerful but has a steep learning curve. SeaORM is good but uses runtime async. We built SQLModel Rust for structured concurrency with asupersync from the ground up.
Control. We need deep integration with asupersync's capability context for cancel-correct operations. Existing drivers don't support our concurrency model.
Not yet, and the README tries not to overstate it. Every core path exists and is unit-tested (about
2,000 tests): query execution, connection pooling, transactions, migrations, and drivers for
PostgreSQL, MySQL, SQLite, and FrankenSQLite. End-to-end coverage against live PostgreSQL and MySQL
runs in CI's integration job; the pre-1.0 API may still change, and the tracker (bv --robot-triage)
lists the remaining proof and hardening work honestly.
No. We use asupersync exclusively. Tokio's model doesn't support structured concurrency the way we need.
Not currently. The entire design assumes async operations with capability contexts.
Please don't take this the wrong way, but I do not accept outside contributions for any of my projects. I simply don't have the mental bandwidth to review anything, and it's my name on the thing, so I'm responsible for any problems it causes; thus, the risk-reward is highly asymmetric from my perspective. I'd also have to worry about other "stakeholders," which seems unwise for tools I mostly make for myself for free. Feel free to submit issues, and even PRs if you want to illustrate a proposed fix, but know I won't merge them directly. Instead, I'll have Claude or Codex review submissions via gh and independently decide whether and how to address them. Bug reports in particular are welcome. Sorry if this offends, but I want to avoid wasted time and hurt feelings. I understand this isn't in sync with the prevailing open-source ethos that seeks community contributions, but it's the only way I can move at this velocity and keep my sanity.
MIT License (with OpenAI/Anthropic Rider). See LICENSE.
