Skip to main content

Kerness — Kernel for Harness

Kerness — Kernel for Harness.
A multi-agent framework for building your own systems with plug-and-play components.
A Rust crate, with Python bindings over the same kernel.

CI License: MIT Rust 1.88+ Python 3.10+


What Kerness is

Kerness is a multi-agent framework for building your own agent systems. Plug together models, tools, memory, and skills, then define how agents work together. Use the built-in components or bring your own.

Two artifacts, one kernel

Kerness is a Rust crate. crates/kerness/ links no Python, spawns no threads, and runs a whole session on the calling thread — a stack trace from inside a tool handler reaches back to Session::run.

bindings/python/ is a binding: a thin PyO3 layer that decides nothing. It forwards into the same kernel, so a session driven from Python takes the same code path as one driven from main(). What Python adds is what Python callers expect — a Provider you subclass, a lambda as a tool handler, a pydantic model for structured output.

Neither surface is a wrapper around the other's use case, and neither is the "real" one. Pick the language; the framework is the same.

That last part is a rule and not an aspiration: a feature is written in Rust. The installed Python package holds the classes callers subclass, the few the extension cannot declare, and re-exports — never behaviour. Where a feature needs something only the interpreter has, the crate names the need as a trait and the binding installs it at import, so capsys captures a console channel, caplog sees a warning, and mock.patch intercepts a request, with the decision still made in one place.

The split

The kernel owns Your harness declares
Provider transport, retries, backoff Which models each agent uses
Tool dialects (OpenAI / Anthropic / text-fence) Which tools exist and what they do
Prompt assembly and ordering Roles, personas, system prompts, language
The orchestrator loop, phases, turn counting Phase names, round counts, instructions
Termination detection Which tokens end a run
Access decisions on commands and paths The policy those decisions are made against
Memory read/write, context compaction Whether the run may write memory
Session files and resume Where the state file lives
Skill discovery and progressive disclosure Which skills an agent may load

Nothing in the left column is something you should have to write again. Nothing in the right column is something the framework should decide for you.

Plug-and-play components

Every component is an interface with a working default. Use the default, or swap in your own — the rest of the kernel does not notice.

Component Ships with Swap it by
Provider OpenAiProvider, ClaudeProvider, OpenRouterProvider, CustomProvider; OAuth credentials where the vendor offers them implementing the Provider trait — one required method, chat
Channel ConsoleChannel, FileChannel, LogChannel, MultiChannel implementing Channel — one required method, send
Tools cmd, read_file, list_dir, write_memory add_tool for legacy handlers, add_tool_spec for complete specs, or add_contextual_tool for scoped capabilities
Skills challenge, fact-check, summarize, agent-browser dropping a SKILL.md directory on disk
Roles participant, orchestrator a .md file whose frontmatter declares a position:, or inline prose
Personas pragmatic_engineer, devils_advocate a .md file, or inline prose
Gameplans debate, discussion, research a new Markdown file — see below
Access closed by default; a workspace that grants its own contents, glob and regex command allow-lists, and path allow-lists that reach past the workspace an AccessPolicy, plus an approval callback
Memory FileMemory — a plain .md file per scope, read-only unless asked; SummarizingMemory — recent notes verbatim, the rest folded into a running summary at the end of the run implementing MemoryStore — two required methods, read and append — and passing it as memory_store
Session file Versioned JSON snapshots of turns, suspended tools, approvals, and usage session_file — absent means persist nothing

The names are the Rust ones. Python spells the two acronym providers the way Python callers expect — OpenAIProvider, plus OpenAIOAuthProvider and ClaudeOAuthProvider — and a trait to implement becomes a class to subclass; everything else carries the same name in both.

A CustomProvider pointed at any OpenAI-compatible endpoint covers most local inference servers without implementing anything at all.

Providers may speak native tool calling or fall back to text fences. The dialect is resolved per agent, so one session can mix an Anthropic model, an OpenAI model, and a local endpoint that supports no tool calling at all, and the orchestrator sees one normalized stream of calls.

A harness is a Markdown file

The YAML frontmatter is a machine-readable contract the framework validates and enforces. The body below it is the orchestrator's manual, in prose.

---
name: debate
description: Adversarial debate, then a revisit against a neutral summary.
agents:
  orchestrator: { required: true }
  participants: { min: 2, max: 6 }
loop:
  max_turns: 50
  max_rounds: 3
  terminate_on: [END_SESSION, CONSENSUS_REACHED]
  phases:
    - name: think
      rounds: 1
      instruction: Give your own independent opinion. Do not rebut anyone yet.
    - name: argue
      rounds: 2
      instruction: Choose a side and present a forceful argument for it.
    - name: rethink
      rounds: 1
      rethink: true
      instruction: Re-examine your opening position against the summary.
result:
  consensus: { type: bool, description: Whether participants converged. }
  summary:  { type: str,  description: The final neutral summary. }
---

# Debate

You are running an adversarial debate. Your job is to make the disagreement
productive, not to resolve it prematurely.

Everything in that contract is enforced, not advisory: a session with one participant is refused before the first API call, a tools: entry naming a tool nobody registered is an error rather than a silent drop, and every problem is reported at once instead of one per run. The declared result: fields come back as typed values on the session result.

A new harness is a new Markdown file. It is not a new runtime, not a subclass, and not a fork.

Install

Rust — MSRV 1.88, no build script, no system libraries:

[dependencies]
kerness = { git = "https://github.com/xwings/kerness" }

Python — abi3 wheels from CPython 3.10 up, so one build covers every supported interpreter:

pip install kerness                  # runtime
pip install 'kerness[structured]'    # plus pydantic, for OpenAIProvider(output_type=...)

From a checkout, either side. The root is a Cargo workspace, so a Python build starts from the binding's own directory:

cargo test --workspace                           # the kernel

cd bindings/python && maturin develop            # the binding
python -m kerness.selfcheck                      # pass = "OK: all core checks passed"

Set it once on the session, override it per agent

The session is configured before any agent exists, and everything it carries — provider, model, reasoning effort, persona, language, system prompt, memory scope, workspace — is a default. An agent that names none of them inherits every one; an agent that names one overrides it, for itself alone. That is the whole rule, and it has two deliberate exceptions.

Provider and model inherit as a pair. A model name only means something on the backend it was written for, so an agent that brings its own provider must name its own model; inheriting the session's would silently ask one vendor for another's model. It is an error at run(), naming the agent.

The workspace only ever narrows. A session's workspace grants its own contents — every path under it is readable without an allow-list entry — and it is the working directory a command starts in. Unset, it is the directory the program was launched from. allowed_dirs and allowed_files reach past it, which is how a session confined to one project still reads /tmp; the two together are the whole of what a session can touch, and an approval callback cannot add to them. An agent may set a workspace of its own, and it is intersected with the session's rather than replacing it — otherwise an agent stanza would be a way to hand itself more of the filesystem than the session was given.

One further asymmetry, and it is not about inheritance: role has no session default at all. A session-wide role would make every agent the orchestrator at once. role is what an agent is in the session — its position and its job — and it is a built-in name, a path to a .md role file, or that job written out as prose. persona is a different question, who the agent is, and it reaches the prompt and nothing else. Unset, role seats a participant, and prose seats a participant too: only a role file declaring position: orchestrator in its frontmatter can seat the chair, so privilege comes from a declaration and never from a substring somebody wrote.

A run, in Rust

Two providers, four agents, a tool, and two skills — the whole configuration contract in one file.

use std::sync::Arc;

use kerness::access::AccessPolicy;
use kerness::provider::{
    ClaudeConfig, ClaudeCredential, ClaudeProvider, OpenAiConfig, OpenAiProvider,
};
use kerness::tooling::Arguments;
use kerness::{Agent, ConsoleChannel, Provider, ReasoningEffort, Session, SessionConfig};
use serde_json::{json, Value};

let openai: Arc<dyn Provider> = Arc::new(OpenAiProvider::new(OpenAiConfig {
    api_key: std::env::var("OPENAI_API_KEY")?,
    ..Default::default()
})?);
let claude: Arc<dyn Provider> = Arc::new(ClaudeProvider::new(ClaudeConfig {
    credential: ClaudeCredential::ApiKey(std::env::var("ANTHROPIC_API_KEY")?),
    ..Default::default()
}));

// Everything on the config is a default. Agents fill in from it at `run()`.
let mut session = Session::new(SessionConfig {
    gameplan: "research".to_string(),
    topic: "Should the cache be write-through?".to_string(),
    provider: Some(openai),
    model: Some("gpt-4o".to_string()),
    reasoning_effort: ReasoningEffort::High,
    channel: Some(Arc::new(ConsoleChannel::default())),
    memory: "/srv/work/notes.md".to_string(),     // the scope the store is asked for
    memory_write: true,                           // ...and, here, may append to
    memory_store: None,                           // None is FileMemory: a scope is a path
    session_file: Some("/srv/work/run.json".to_string()), // None persists nothing
    access_policy: Some(AccessPolicy {
        workspace: Some("/srv/work".to_string()),  // this tree, and nothing else
        allowed_dirs: vec!["/tmp".to_string()],    // ...except what is named here
        allowed_commands: vec!["rg *".to_string()],
        ..AccessPolicy::new()
    }),
    ..Default::default()
})?;

// A tool of your own. The handler is a closure; the kernel translates the
// schema into whichever dialect each agent's provider speaks, parses the call
// back out, feeds the result in, and stops a model that loops on bad calls.
session.add_tool(
    "lookup_price",
    "Look up the current price of a ticker.",
    json!({"type": "object",
           "properties": {"ticker": {"type": "string"}},
           "required": ["ticker"]}),
    Arc::new(|args: &Arguments, _actor: &str| {
        let ticker = args.get("ticker").and_then(Value::as_str).unwrap_or_default();
        Ok(format!("{ticker} is at 41.20"))
    }),
)?;
session.add_skill("fact-check")?;
session.add_skill("summarize")?;

// Alice names nothing but a persona, so she takes every default above.
session.add_agent(Agent {
    persona: Some("pragmatic_engineer.md".to_string()),
    ..Agent::new("Alice")
})?;

// Bob overrides one thing — a cheaper model on the same backend — and confines
// himself to a directory inside the session's workspace.
session.add_agent(Agent {
    persona: Some("devils_advocate.md".to_string()),
    workspace: Some("/srv/work/scratch".to_string()),
    ..Agent::new("Bob").with_model("gpt-4o-mini")
})?;

// Carol brings her own vendor, so she must name her own model: "gpt-4o" means
// nothing to Anthropic, and inheriting it silently would be the wrong answer.
// `with_provider` takes both for that reason. Effort is portable, so it
// inherits or overrides on its own.
session.add_agent(Agent {
    reasoning_effort: Some(ReasoningEffort::Medium),
    skills: Some(vec!["fact-check".to_string()]),   // Alice and Bob get both
    ..Agent::new("Carol").with_provider(claude, "claude-sonnet-4-5")
})?;

// The chair, seated by a role file that declares `position: orchestrator`.
session.add_agent(Agent::new("Mod").with_role("orchestrator"))?;

let result = session.run()?;
println!("{}", result.summary());
println!("{:?}", result.fields.get("findings"));   // the gameplan's declared fields
println!("{} rounds, ended on {}", result.rounds_run, result.end_reason);

Full file: crates/kerness/examples/debate.rs — cargo run -p kerness --example debate.

To watch a session run without an API key, use offline_debate, which drives the debate gameplan against a scripted provider:

cargo run -p kerness --example offline_debate    # no key, no network

Other examples cover per-agent providers, memory, structured output, custom tools and channels, host control, and durable approvals.

The same run, in Python

The binding mirrors the crate, in the shapes Python callers expect: keyword arguments instead of a config struct, a plain callable instead of a closure in an Arc, a dataclass instead of an AccessPolicy literal. Same kernel, same resolution rules, same errors.

import os

from kerness import (
    AccessPolicy, ClaudeProvider, ConsoleChannel, OpenAIProvider, Session,
)

openai = OpenAIProvider(api_key=os.environ["OPENAI_API_KEY"])
claude = ClaudeProvider(api_key=os.environ["ANTHROPIC_API_KEY"])

session = Session(
    gameplan="research",
    topic="Should the cache be write-through?",
    provider=openai,
    model="gpt-4o",
    reasoning_effort="high",
    channel=ConsoleChannel(),
    memory="/srv/work/notes.md",
    memory_write=True,
    session_file="/srv/work/run.json",  # resumable; omit to persist nothing
    access_policy=AccessPolicy(
        workspace="/srv/work",      # this tree, and nothing else
        allowed_dirs=["/tmp"],      # ...except what is named here
        allowed_commands=["rg *"],
    ),
)

prices = {"KRN": "41.20"}
session.add_tool(
    "lookup_price",
    "Look up the current price of a ticker.",
    {"type": "object",
     "properties": {"ticker": {"type": "string"}},
     "required": ["ticker"]},
    lambda args: prices.get(args["ticker"], "unknown"),
)
session.add_skill("fact-check")
session.add_skill("summarize")

session.add_agent("Alice", persona="pragmatic_engineer.md")
session.add_agent("Bob", persona="devils_advocate.md",
                  model="gpt-4o-mini", workspace="/srv/work/scratch")
session.add_agent("Carol", provider=claude, model="claude-sonnet-4-5",
                  reasoning_effort="medium", skills=["fact-check"])
session.add_agent("Mod", role="orchestrator")

result = session.run()
print(result.summary)
print(result.fields["findings"])   # the gameplan's declared fields
print(result.rounds_run, result.end_reason)

The add_* calls return the session, so registration chains if you would rather write it that way.

Unrecovered provider failures end the session. Rust Session::run returns the original error; Python session.run() raises its corresponding exception. HTTP rejections preserve the server's status code, URL and response body in ProviderHTTPError attributes status_code, url and body. For partial history and usage after failure, use an owned SessionRun, which retains the error in outcome.error. Successful HTTP responses containing API errors, model refusals or content filters raise ProviderError with the reported reason and server details. Invalid JSON is a response error; connection and body IO failures raise ProviderNetworkError. Empty or invalid structured output includes the stop reason when the server supplies it.

Host-controlled runs

Session::start(RunOptions) transfers configuration into an owned SessionRun. Choose RunMode::HostDriven to select participants yourself; an orchestrator is needed only when the gameplan explicitly requires one. step(RunInput) returns progress, an identified waiting state, or a typed terminal outcome with partial history, result diagnostics, usage, and any original framework error.

Each sequential step dispatches at most one engine-selected logical provider operation, tool invocation, compaction, or maintenance scope, and may settle several local effects. Providers may retry synchronously, and callbacks can call provider APIs; supplied metering seams count those calls against the run. An explicit concurrent batch can dispatch a bounded group of provider operations in one step, which waits for that group before returning. Events report progress; inputs select an agent, add user text at a turn boundary, answer an approval, reconcile an interrupted action, or finish with a host-supplied result. Finish validates the declared result without an implicit agent or judge call to generate it; configured memory maintenance may still call a provider. A separate RunControl requests cooperative cancellation.

Custom contextual handlers receive trusted actor/run/turn/call identity and file, command, and memory capabilities. Those handles enforce the actor's policy and expire when the invocation ends. A side-effect-free preflight can request confirmation before the handler runs. A saved pending approval resumes with its same request ID and completed tool results; an interrupted action with an unknown outcome requires reconciliation instead of automatic replay.

RunOptions also accepts operation/tool limits, host-supplied token pricing, and measured token/cost/time thresholds. Unsupported hard token/cost caps are rejected. Cancellation cannot forcibly interrupt an arbitrary blocking provider or user callback.

Run the complete offline examples:

cargo run -p kerness --example host_control
cargo run -p kerness --example resume_approval
python bindings/python/examples/host_control.py

host_control.rs selects one participant, observes events, and supplies a validated result. resume_approval.rs saves a pending confirmation, drops and rebinds the run, then completes two tools once each. Both create and clean their own temporary workspaces.

The Python example uses the same engine through session.start(mode="host_driven") and run.step(...). Inputs are dictionaries such as {"kind": "select_agent", "agent": "Advisor", "instruction": "Recommend a policy."}; outcomes carry status: "progress", "waiting", or "finished". ARCHITECTURE/modules/python-bindings.md documents the thin API, callback signatures, and handle lifetimes.

Concurrent batches

To let the orchestrator assign independent tasks together, add a concurrency limit to the gameplan's YAML frontmatter:

loop:
  max_concurrent_agents: 3

The default is 1. Above one, the bundled orchestrator prompt explains how to select a batch by replying with this explicit block:

```kerness
{"parallel":[{"agent":"Alice","instruction":"Compare storage options."},{"agent":"Bob","instruction":"Estimate operating costs."}]}
```

The host-driven Python equivalent is:

run.step({
    "kind": "select_agents",
    "assignments": [
        {"agent": "Alice", "instruction": "Compare storage options."},
        {"agent": "Bob", "instruction": "Estimate operating costs."},
    ],
})

Rust uses RunInput::SelectAgents with a Vec<AgentAssignment>. Members must be distinct participants still owing a turn this round and fit the remaining turn budget. The concurrency limit bounds execution; a batch may contain more members than that limit.

Rust callers constructing LoopSpec with a struct literal must include the new field or use ..Default::default(). Existing gameplans keep the default of one.

Every member starts with the same conversation snapshot and keeps its own tool scratch and loaded skills. Provider calls overlap in bounded groups; tool effects and observers run serially on the caller's thread. Approval pauses the batch. Once the batch settles, results enter the conversation in assignment order and the orchestrator continues with all of them. Successful sibling results survive failure or cancellation, and checkpoints retain buffered work and pending approvals. Concurrent tasks should be independent: serial tool effects do not resolve conflicting file edits.

The Python run() and step() wrappers release the GIL while the Rust engine works. Cancellation remains cooperative, and a step waits for its in-flight provider calls to return. The lower-level OrchestratorLoop::run callback adapter executes batches serially; Session uses the concurrent owned engine.

What the kernel does while it runs

Neither of the above is a different runtime, so the following holds for both.

Skills use progressive disclosure: prompts carry only names and descriptions, and the full instructions load on demand through a turn-local Skill tool. A skill also says which tools the turn should hold: allowed-tools: narrows it, requires-tools: adds back what the skill cannot work without, out of whatever the session registered. A skill requiring a tool nobody registered is refused before the first call rather than quietly doing nothing. When the policy trusts bundles, loading a skill also grants read access to its own directory.

There is no daemon and no server. A run given a session file saves coherent execution boundaries and resumes from that file when the host reattaches its providers and handlers. Resume checks identity and the saved runtime contract before continuing; valid version 1 turn-boundary snapshots are migrated.

Layout

Cargo.toml       # the only manifest at the root
crates/kerness/  # the kernel, pure Rust — no PyO3, no Python
  src/           #   kernel implementation, with unit tests inline
  tests/         #   integration tests over the public API
  examples/      #   10 runnable Rust harnesses, three needing no key
  assets/        #   bundled gameplans, roles, personas, skills
bindings/python/ # everything the wheel is built from
  pyproject.toml #   the wheel's manifest — `pip install .` runs here
  src/           #   the PyO3 extension module, kerness._core
  kerness/       #   the Python package: the subclassable classes, shims, assets
  tests/         #   pytest suite, over the binding
  examples/      #   runnable Python harnesses
ARCHITECTURE/    # shared agent rules, subsystem modules, conditional topics

The bundled debate, discussion, and research gameplans are worked examples of the contract, not the product.

Testing

Each suite proves its own layer. The Rust integration tests compile against the crate's public API exactly as a dependent does, so a break in that surface fails a test here rather than a downstream build; the pytest suite proves the binding carries the kernel's behaviour across the FFI boundary intact.

cargo fmt --all -- --check
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspace                     # unit and public API integration suites
cargo build -p kerness --examples          # every example still compiles
cargo run -p kerness --example offline_debate   # a whole session, no key

python -m pytest bindings/python/tests -q      # binding suite
python -m kerness.selfcheck                    # exit 0
ruff check bindings/python

CI runs all of it on every push, on Rust stable and the 1.88 MSRV floor, and on Python 3.10 and 3.13.

Documentation

ARCHITECTURE.md is the entry point for project constraints, shared agent rules, and task routing. Subsystem modules under ARCHITECTURE/ describe contracts, source evidence, and verification commands; conditional topics expand the relevant checks.

License

MIT. See LICENSE.

Release files for kerness 0.1.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for kerness 0.1.2
File Size Uploaded
kerness-0.1.2.tar.gz 514.0 kB Details

Built distributions (wheels)

Table of built distributions (wheels) for kerness 0.1.2
File
kerness-0.1.2-cp310-abi3-win_amd64.whl CPython 3.10 abi3 Windows x86-64 Details
kerness-0.1.2-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.10 abi3 Linux glibc 2.17+ x86-64 Details
kerness-0.1.2-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl CPython 3.10 abi3 Linux glibc 2.17+ ARM64 Details
kerness-0.1.2-cp310-abi3-macosx_11_0_arm64.whl CPython 3.10 abi3 macOS 11.0+ ARM64 Details
kerness-0.1.2-cp310-abi3-macosx_10_12_x86_64.whl CPython 3.10 abi3 macOS 10.12+ x86-64 Details

Total release size: 15.7 MB

Release files / kerness-0.1.2.tar.gz

Download URL kerness-0.1.2.tar.gz
Size 514.0 kB
Tags Source
SHA-256 checksum
How to use checksums
8803ed8d4b871a1d3a500603e8953b7fde7d9d829164b76ba3dfefb50e1e9518
BLAKE2b-256 checksum
How to use checksums
4665af19ec972819d586c716c6b4041b6decf08481fcd95aebf46c37bbede0c4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via maturin/1.15.0

Release files / kerness-0.1.2-cp310-abi3-win_amd64.whl

Download URL kerness-0.1.2-cp310-abi3-win_amd64.whl
Size 3.2 MB
Tags CPython 3.10 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
92706b05c453bc19e945f7e8e8685bba0e5224bc543682e332dea3f4f638356c
BLAKE2b-256 checksum
How to use checksums
5f81e8e212dafae12560e16d7bf6be07c6229786b00fb3250a5aa4b03cd71e2b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via maturin/1.15.0

Release files / kerness-0.1.2-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL kerness-0.1.2-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 3.1 MB
Tags CPython 3.10 Linux glibc 2.17+ x86-64 abi3
SHA-256 checksum
How to use checksums
30cda51041a8c781d997e398c11cd6992dbae0e97df6e7e80b4a228459933385
BLAKE2b-256 checksum
How to use checksums
e36b0c9c7407aa14ce80220d4eae77de00747a14e0b4a7942b52c4473e5f3f38
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via maturin/1.15.0

Release files / kerness-0.1.2-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL kerness-0.1.2-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 3.0 MB
Tags CPython 3.10 Linux glibc 2.17+ ARM64 abi3
SHA-256 checksum
How to use checksums
473f97663f605c6fc51727f754f89ac1fae99dd14097531ee840e35163ff6329
BLAKE2b-256 checksum
How to use checksums
0420cf8634e8aca908d9cc99e47ac2fa15de847bf5772c4fd0cd67be24b6acf1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via maturin/1.15.0

Release files / kerness-0.1.2-cp310-abi3-macosx_11_0_arm64.whl

Download URL kerness-0.1.2-cp310-abi3-macosx_11_0_arm64.whl
Size 2.8 MB
Tags CPython 3.10 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
05a08555c97bb0f61bda60f27347bdad4f80d46d4ee52dfd706637c1c81bf33c
BLAKE2b-256 checksum
How to use checksums
9712c1fba93b12f3c9bd8b3a9513d443128a9d089f0e5c5791bd0e23fb0c7326
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via maturin/1.15.0

Release files / kerness-0.1.2-cp310-abi3-macosx_10_12_x86_64.whl

Download URL kerness-0.1.2-cp310-abi3-macosx_10_12_x86_64.whl
Size 3.0 MB
Tags CPython 3.10 abi3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
5f586f0005456c9aff94a089844548a529c3c4c47205a21556382f9ebfdd39bc
BLAKE2b-256 checksum
How to use checksums
71985baf83c05d1a174502385dfda7c59133e418ee36ed5f826f9b01cf85d3b9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via maturin/1.15.0

Release history Release notifications | RSS feed

This release

0.1.2 This release

6 release files

0.1.1

6 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page