Skip to content

Contributing

Thanks for looking. newt is early and changes quickly, so open an issue before starting anything large.

You need the Rust toolchain pinned in rust-toolchain.toml (1.99.1, with clippy and rustfmt; rustup installs it on first use) and Docker for Postgres.

Terminal window
docker compose up -d --wait # Postgres 18 on localhost:55432
export DATABASE_URL=postgres://newt:newt@localhost:55432/newt
export NEWT_DATABASE_URL=$DATABASE_URL
  • DATABASE_URL is read by the tests: each #[sqlx::test] creates a throwaway database from it and applies migrations/.
  • NEWT_DATABASE_URL is the variable server.example.toml names for the server’s own connection (database.url_env).

Run the server against the example config:

Terminal window
NEWT_CONFIG=server.example.toml cargo run -p newt-server

The example config also needs OPENAI_API_KEY, ANTHROPIC_API_KEY and ASSISTANT_TOKEN set (any value works until a turn calls the model). The README walks through the demo.

CI (.github/workflows/ci.yml) runs these against Postgres 18, in this order. Run them locally before you push:

Terminal window
cargo fmt --check
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspace
cargo deny check # licences, yanked crates, wildcard versions, unknown registries
cargo machete # unused dependencies

CI then builds the release binary, starts it with server.example.toml, waits for /healthz to return 200 and sends it SIGTERM, which must exit cleanly. cargo deny and cargo machete are installed with cargo install cargo-deny cargo-machete.

These come from [workspace.lints] in Cargo.toml. With -D warnings, every warning below fails CI.

  • No unsafe (forbid).
  • No unwrap(), panic!, todo!, unimplemented! or dbg! (deny). expect() is a warning: use it only for a true invariant and say why in the message. Tests may unwrap by opening the file with #![expect(clippy::unwrap_used, reason = "...")].
  • No println! or eprintln! (deny). Use tracing.
  • No holding a Mutex guard or a RefCell borrow across .await (deny).
  • No #[allow(...)] (deny). Suppress a lint with #[expect(lint, reason = "...")]; the reason is required.
  • Clippy all and pedantic are on. Three pedantic lints are switched off: module_name_repetitions, missing_errors_doc, must_use_candidate.
  • Public types implement Debug (missing_debug_implementations).
  • #[must_use] results must be used (unused_must_use is deny).
  • Large futures, Rc<Mutex<_>> and clone() on reference-counted pointers are warnings.
  • Errors: thiserror enums in newt-core, anyhow with .context(...) in newt-server. Do not drop the source error.
  • Fail loudly on bad config or a bad provider response instead of falling back.
  • Prefer concrete types. Add a trait only where the design already has a seam (EventSink, Model, ToolHost, Journal).
  • Do not block the async runtime. Use tokio’s APIs or spawn_blocking.
  • Comments only for a constraint the code cannot express.
  • A new dependency needs a reason in the pull request, and cargo deny check must pass.

Tests assert behaviour on inputs. A bug fix comes with a test that fails without the fix. See the README’s “Running the tests” section.

  • One change per pull request, with a description of what changed and why.
  • Rebase on main and run the checks above first.
  • If you change the context-provider protocol, the MCP _meta fields or a config key, update Protocol or Configuration in the same pull request.
  • By contributing you agree your work is licensed as Apache-2.0 OR MIT, like the rest of the project.

For security issues, see Security. Please follow CODE_OF_CONDUCT.md.