Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

cliver-core

The embeddable agent core behind CLIver: a Re-Act loop with tool calling, per-purpose model bindings, media capabilities, and first-class MCP support.

No CLI, no database, no config files — you pass Python objects in and get messages back.

Install

pip install cliver-core==0.3.0a5          # core library + MCP client (stdio / streamable HTTP)
pip install "cliver-core[local]==0.3.0a5" # + in-process local embedding / rerank models

The project is on pre-releases, so pin the exact version (an exact pin selects the pre-release without --pre). Python 3.10+.

Quick start

import asyncio

from cliver_core import AgentCore, ModelBinding, ModelBindings


async def main():
    agent = AgentCore(
        ModelBindings(
            llm=ModelBinding.llm(
                model="deepseek-v4-flash",
                api_key="...",
                base_url="https://api.deepseek.com",
            )
        )
    )
    response = await agent.chat("Summarize the Re-Act pattern in one sentence.")
    print(response.message.content)
    await agent.aclose()


asyncio.run(main())

Ready-made bindings for live-verified providers live in cliver_core.presets:

from cliver_core import ModelBindings, presets

bindings = ModelBindings(llm=presets.deepseek(api_key="..."))
agent = AgentCore(bindings)

Tools

cliver_core.tools ships nine file, shell, todo, and web tools. Register them directly:

from cliver_core.tools import discover_builtin_tools

agent = AgentCore(bindings, builtin_tools=discover_builtin_tools())

ToolRegistry and the @tool decorator build custom tools.

Capabilities

Every purpose works standalone — no AgentCore required:

from cliver_core import ModelBinding
from cliver_core.capabilities import embed, generate_image

vectors = await embed(
    texts=["hello", "world"],
    binding=ModelBinding.embed(model="text-embedding-3-small", api_key="...", base_url="..."),
)

image = await generate_image(
    prompt="a red panda on a skateboard",
    binding=ModelBinding.image(model="gpt-image-1", api_key="...", base_url="..."),
)

Purposes: image, video, text-to-speech, speech-to-text, embed, rerank. Long generations can return a MediaJob future via AgentCore's submit_* variants.

Local models

Run embedding and rerank models in-process — no endpoint. The backends pull torch, so they are opt-in:

pip install "cliver-core[local-st]"     # sentence-transformers embeddings
pip install "cliver-core[local-flag]"   # FlagEmbedding embeddings + rerank
pip install "cliver-core[local]"        # both
from cliver_core.capabilities import embed, rerank
from cliver_core.local import (
    LocalEmbeddingProvider,
    LocalRerankProvider,
    local_embed,
    local_rerank,
)

embedder = LocalEmbeddingProvider(
    model="BAAI/bge-base-zh-v1.5",  # Hugging Face id or local directory
    backend="sentence-transformers",  # required; never inferred from the model name
)
reranker = LocalRerankProvider(
    model="BAAI/bge-reranker-v2-m3",
    backend="flag-embedding",
)

vectors = await embed(
    texts=["hello", "world"],
    binding=local_embed(provider=embedder, normalize=True),
)
items = await rerank(
    query="hello",
    documents=["hi", "hey"],
    binding=local_rerank(provider=reranker, top_k=1),
)

await embedder.close()
await reranker.close()

A provider owns one loaded model and is meant to be long-lived: create it once, share it across bindings and calls, and close it at shutdown. There is no process-global model cache. ModelBinding.aclose() never closes a provider you supplied.

The API is deliberately explicit: model, backend, device, and dtype are parameters, not name-based defaults. Call options are generic — dim (optional output-size check), normalize (off by default; raw vectors/scores), batch_size, and top_k (rerank). Query/document prefixes and instruction templates belong to the application.

provider.info() reports the resolved model directory, revision, device, dtype, and load time. Hugging Face downloads honor HF_TOKEN, cache_dir, revision, and local_files_only.

MCP

from cliver_core.mcp import MCPClient

client = MCPClient.from_discovery()  # ~/.cliver/mcp.json, .cliver/mcp.json, drop-ins
agent = AgentCore(bindings, mcp_client=client)

MCP tools are advertised to the model as mcp__<server>__<tool>. Prompts and resources are read on demand — list_prompts, get_prompt, list_resources, read_resource — never registered as tools.

Skills

Agent Skills: a skill is a directory with a SKILL.md (frontmatter + instructions) plus optional scripts/, references/, assets/. cliver-core reads skills from explicit sources the application supplies — a directory of skill directories or a single skill directory; nothing is discovered implicitly, and later sources override earlier ones by name.

from cliver_core import AgentCore, SkillManager

skills = SkillManager(sources=["./skills"])  # routing metadata: skills.list_skills()
agent = AgentCore(bindings, builtin_tools=[...], skill_manager=skills)

result = await agent.run_skill("Refund", "订单 12345 想退货", conversation=history)

run_skill (name or an already-loaded Skill object) appends an activation header — skill name and location, so bundled scripts/ are runnable — plus the skill body to the single system message and runs the normal Re-Act loop; a declared allowed-tools frontmatter narrows the advertised tools to that set — a non-empty allowlist matching zero available tools fails fast, while an absent or empty list leaves tools unchanged. The other call parameters mirror chat() (except tool_filter and skill_dispatch). run_skill() adds none of the model-driven skill tools (FindSkills, ActivateSkill, ReadSkillFile); an allowed-tools entry naming one of those is unavailable in this path.

Progressive disclosure: list_skills() exposes only routing metadata (name + description — format_skill_catalog() renders the catalogue for prompts); the body enters context only when a skill is activated; bundled files are listed (skill.files) and readable on demand (skill.read_file("references/policy.md"), path escapes rejected).

With a SkillManager configured, chat()/stream() give the model that catalogue in the system prompt (skill_summary_limit, default 10), FindSkills(query, limit) to search the full catalogue, and ActivateSkill(name) to load a skill's body as a tool result for the rest of the run. Activation is one skill at a time; a declared allowed-tools narrows the advertised tools for the remaining iterations, and an activated skill that bundles files also gets ReadSkillFile(path) — confined to that skill's directory (escapes refused, UTF-8 text, 256 KiB cap), while workspace files stay on Read. Apps may rank the catalogue with skill_selector(skills, user_input), replace lexical search with skill_search(query, skills), and narrow both candidate pools with candidate_selector(tools, skills, user_input) / tool_selector(tools, user_input). run_skill() disables dispatch for its run.

The dependency-free frontmatter parser covers the spec's surface plus what cross-agent skills (workbuddy, accio, ...) carry — flat scalars, quoted strings, > / | blocks, and nested maps of any depth (unknown keys are kept in skill.frontmatter for forward compatibility; no anchors or - sequences). Validation follows the agentskills.io rules (name pattern, 64/1024/500-char limits) as load-time warnings; skills stay loadable unless name or the frontmatter itself is missing. (Anthropic's product-specific extras — reserved words in names — are not enforced.) Script execution sandboxes remain future work.

Pipelines

SkillPipeline runs an ordered list of steps sequentially, each in its own fresh context. The caller defines the order — no LLM decides it. A step runs a skill (via run_skill()) or a raw prompt (via chat()); its input is composed from the original input and the prior results. A synthesis step is an ordinary final step that reads the earlier outputs.

from cliver_core import PipelineStep, SkillPipeline


async def log(event) -> None:
    print(event.event, getattr(event, "step_name", None))


pipeline = SkillPipeline(
    [
        PipelineStep(name="market", skill="market-analysis"),
        PipelineStep(name="competitors", skill="competitor-scan"),
        PipelineStep(
            name="economics",
            skill="unit-economics",
            input_builder=lambda ctx: f"{ctx.input}\n\nMarket:\n{ctx.result('market').content}",
        ),
        PipelineStep(
            name="report",
            skill="report-writing",
            input_builder=lambda ctx: (
                "\n\n".join(f"## {r.name}\n{r.content}" for r in ctx.results) + f"\n\n## Idea\n{ctx.input}"
            ),
        ),
    ],
    agent=agent,
    on_event=log,  # pipeline lifecycle + forwarded tool events
)

result = await pipeline.run("A subscription box for office plants")
print(result.ok, result.final.content)
  • Results — PipelineResult.results is an ordered tuple of StepResults (name, content, optional metadata, usage, error); .final is the last result and .ok is true only when every step ran and none failed.
  • Input builders — PipelineContext carries the original input and prior results (ctx.result(name), ctx.previous). Builders may be sync or async. The default input is the pipeline input.
  • Step prompts — PipelineStep.prompt is raw step instructions appended to the single system message (as extra instructions alongside a skill, or as the whole instruction when no skill is given).
  • Failure policy — on_failure=FailurePolicy.STOP (default) stops at the first failed step and sets PipelineResult.stopped; FailurePolicy.CONTINUE records the error and runs the next step. Later steps can inspect ctx.result(name).error.
  • Runners — a step may inject a runner(step, step_input, context, emit) to use a different AgentCore instance, model, tools, or skill set; pass on_event=emit so its tool events are forwarded. Custom runners may return StepOutput (content + metadata + usage), CLIverResponse, or a plain string.
  • Events — the pipeline emits PipelineEvents (pipeline.start, pipeline.step_start, pipeline.step_end, pipeline.step_error, pipeline.end) and forwards each step's ToolEvents. Cancelling the task emits pipeline.end with {"cancelled": true} and re-raises CancelledError.

Permissions & execution policy

Two independent axes, both opt-in — with neither set, every tool runs exactly as before.

Permission axis (may this tool run?). Every tool carries a category: SAFE or ASK. Read-only builtins (Read, LS, Grep, TodoRead/TodoWrite) are SAFE; mutating ones (Write, Bash, Exec, WebFetch) are ASK by default. MCP tools map the spec's self-declared readOnlyHint annotation to SAFE — a hint, never a permission boundary. An application-supplied async permission_callback is consulted before any ASK tool runs:

from cliver_core import AgentCore, Decision, EnvSpec, ExecutionPolicy, SAFE_ENV_VARS, ToolCategory


async def ask_user(ctx) -> Decision:  # ctx: tool_name, args, server, category
    answer = await my_ui.confirm(f"Run {ctx.tool_name}?")
    return Decision.allow() if answer else Decision.deny("user said no")


agent = AgentCore(
    bindings,
    builtin_tools=[...],
    permission_callback=ask_user,  # None → ASK tools run unchecked
    execution_policy=ExecutionPolicy(  # environment axis, ambient per call
        env=EnvSpec(mode="allowlist", names=set(SAFE_ENV_VARS)),
        cwd=project_root,
        write_paths=[project_root / "src"],
    ),
    safety_overrides={"mcp__github__create_issue": ToolCategory.ASK},
)

A denial never raises: it returns {"error": "Permission denied: …", "denied": true} to the model through the normal tool-result path and also emits ToolEventType.PERMISSION_DENIED through on_event before the call's TOOL_END. Callback exceptions fail closed. Denials share the consecutive cap with errors (5 in a row stops the loop). safety_overrides reclassify individual tools per AgentCore instance — builtins are shared singletons and never mutated.

Environment axis (where does it run, what does it see?). Pass execution_policy= to the constructor or per chat()/stream() call (call-site wins). While ambient: Bash and Exec run with the policy env (allowlist/denylist/inherit) and pinned cwd — Bash logs an INFO line when the pin overrides its directory argument; Read/LS/Grep/Write enforce read_paths/write_paths (resolved against the policy cwd, symlinks followed). run_skill applies a call-site policy verbatim; otherwise an agent-level policy is reused with cwd pinned to the skill's directory.

The two axes are complementary, not a hard sandbox: read_paths/write_paths bind the file tools only — an approved Bash/Exec call runs as your process user and can touch anything that user can, under the filtered env and pinned working directory. Use the permission axis (callback, safety_overrides) to decide whether shell access is granted at all; the paths are a guardrail against accidental access, not an OS-level confinement.

License

Apache-2.0

Metadata

Release files for cliver-core 0.3.0a5

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

Source distribution (sdist)

Source distribution for cliver-core 0.3.0a5
File Size Uploaded Provenance
cliver_core-0.3.0a5.tar.gz 206.3 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for cliver-core 0.3.0a5
File Interpreter ABI Platform
cliver_core-0.3.0a5-py3-none-any.whl Python 3 none any Details

Total release size: 373.8 kB

Release files / cliver_core-0.3.0a5.tar.gz

Download URL cliver_core-0.3.0a5.tar.gz
Size 206.3 kB
Tags Source
SHA-256 checksum
How to use checksums
702ad24915b28ce2bf95b84351c505e96dfeb14d6a94531a2a5c51bfd1f3950e
BLAKE2b-256 checksum
How to use checksums
4d454fe3333f9173f42d2c0a4280371756a17f6966450d95eb6aefe76f171f55
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.9 {"installer":{"name":"uv","version":"0.12.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / cliver_core-0.3.0a5-py3-none-any.whl

Download URL cliver_core-0.3.0a5-py3-none-any.whl
Size 167.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d5d1e505de3657c2628b755dfc337858235b9878dde95933d1eec95617883141
BLAKE2b-256 checksum
How to use checksums
9121d87db71c94601126e89d34de41d7f7a0b9d854c75cacb8037c0e7fc8dd42
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.9 {"installer":{"name":"uv","version":"0.12.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Security for cliver-core 0.3.0a5

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

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