Skip to main content
This page is a contributor’s map of Cognis. If you’re adding a new feature, this is where you check that you’re putting it in the right crate and following the existing shape.

Workspace map

Dependency rules (strict)

These are enforced by the build:
  • cognis-core has zero internal-crate dependencies.
  • cognis-llm, cognis-rag, cognisgraph, cognis-trace each depend only on cognis-core (and cognis-macros where they use a derive). They do not depend on each other.
  • cognis is the only crate that depends on the four siblings together.
  • cognis-macros is proc-macro-only; consumed by everything that wants the derives.
If a feature could plausibly live in two places, default to the smaller crate (closer to cognis-core).

Where new code goes

Agent orchestration middleware (filesystem, memory, subagent, planning, skills, todo, prompt caching) belongs in cognis. Agent policy building blocks (retry, rate limiting, PII, redaction, model fallback) live in cognis too — they share the same Middleware trait.

Design patterns (mandatory)

These shape every feature in the codebase. Follow them when adding new code.

Traits, not classes

Each capability is a trait with a small required surface and generous provided defaults. New Memory impls implement read/write/clear and inherit seed. New RateLimiter impls implement acquire(estimated_tokens) and inherit the policy combinators. When adding a new variant of an existing trait, mirror the builder methods and naming of its siblings. A new Memory variant ships with_system(...) because the others do.

Builders return Self

with_X(self, x) -> Self. Constructors don’t take optional args — promote them to builder methods.

Enums for closed sets

Where Python uses union types or magic strings, Rust uses enums with exhaustive matching. Examples: Message, ContentPart, ToolChoice, StreamMode, Goto, Durability.

Generic Runnable, not Value-typed

Runnable<I, O> is generic — never Value-typed at trait boundaries. Use serde_json::Value only at system edges (user input, persistence, wire serialization). Heterogeneous composition uses type-erased wrappers (DynRunnable).

Errors are actionable

Each crate below the umbrella defines its own error type via thiserror. Cross-crate errors use From conversions — cognis_graph::Error wraps cognis_core::CognisError. The umbrella cognis crate does not depend on thiserror directly. When adding a new error type there, hand-roll Display and Error.

Async by default

All I/O-bound trait methods are async via #[async_trait]. No sync compat layer; callers use block_on if they need to.

Feature flags for optional deps

Every external integration is behind a feature flag. The core crates compile with no network deps. New providers, vector stores, and exporters all gain a feature.

Code quality rules

  • All public types, traits, and functions get /// doc comments.
  • No unwrap() or expect() in library code — propagate errors with ?.
  • No println! in library code — use tracing macros.
  • Clippy passes: cargo clippy --workspace --features all-providers -- -D warnings.
  • Format with cargo fmt before every commit.
  • New public API gets at least one unit test and one doc-test.

What NOT to port from Python

See also

Adding a provider

The walkthrough for a new LLM client.

PR guidelines

The PR checklist that maps to these rules.

Reference → cognis

The umbrella’s re-export surface.