cairnkit 0.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cairnkit-0.5.0/.github/workflows/ci.yml +27 -0
- cairnkit-0.5.0/.github/workflows/release.yml +55 -0
- cairnkit-0.5.0/.gitignore +20 -0
- cairnkit-0.5.0/AGENTS.md +24 -0
- cairnkit-0.5.0/CHANGELOG.md +99 -0
- cairnkit-0.5.0/CONTRIBUTING.md +31 -0
- cairnkit-0.5.0/LICENSE +22 -0
- cairnkit-0.5.0/NOTICE.md +31 -0
- cairnkit-0.5.0/PKG-INFO +234 -0
- cairnkit-0.5.0/README.md +205 -0
- cairnkit-0.5.0/README.zh-CN.md +67 -0
- cairnkit-0.5.0/SECURITY.md +21 -0
- cairnkit-0.5.0/cairnkit/__init__.py +143 -0
- cairnkit-0.5.0/cairnkit/agents.py +111 -0
- cairnkit-0.5.0/cairnkit/compression.py +973 -0
- cairnkit-0.5.0/cairnkit/curator.py +217 -0
- cairnkit-0.5.0/cairnkit/delegate.py +99 -0
- cairnkit-0.5.0/cairnkit/documents.py +421 -0
- cairnkit-0.5.0/cairnkit/guardrails.py +124 -0
- cairnkit-0.5.0/cairnkit/llm/__init__.py +17 -0
- cairnkit-0.5.0/cairnkit/llm/base.py +34 -0
- cairnkit-0.5.0/cairnkit/llm/errors.py +185 -0
- cairnkit-0.5.0/cairnkit/llm/fallback.py +107 -0
- cairnkit-0.5.0/cairnkit/llm/openai_compat.py +281 -0
- cairnkit-0.5.0/cairnkit/llm/scripted.py +54 -0
- cairnkit-0.5.0/cairnkit/loop.py +310 -0
- cairnkit-0.5.0/cairnkit/memory.py +185 -0
- cairnkit-0.5.0/cairnkit/memory_ops.py +201 -0
- cairnkit-0.5.0/cairnkit/pricing.py +37 -0
- cairnkit-0.5.0/cairnkit/prompt.py +27 -0
- cairnkit-0.5.0/cairnkit/py.typed +0 -0
- cairnkit-0.5.0/cairnkit/review.py +55 -0
- cairnkit-0.5.0/cairnkit/search.py +130 -0
- cairnkit-0.5.0/cairnkit/security.py +215 -0
- cairnkit-0.5.0/cairnkit/skills.py +184 -0
- cairnkit-0.5.0/cairnkit/titles.py +75 -0
- cairnkit-0.5.0/cairnkit/tools.py +175 -0
- cairnkit-0.5.0/cairnkit/types.py +58 -0
- cairnkit-0.5.0/docs/approval-demo.md +76 -0
- cairnkit-0.5.0/docs/assets/approval-demo.gif +0 -0
- cairnkit-0.5.0/docs/assets/approval-poster.png +0 -0
- cairnkit-0.5.0/docs/assets/cairnkit-approval-demo.mp4 +0 -0
- cairnkit-0.5.0/docs/hermes-porting.md +211 -0
- cairnkit-0.5.0/docs/launch/README.md +91 -0
- cairnkit-0.5.0/docs/launch/demo.zh-CN.srt +24 -0
- cairnkit-0.5.0/docs/launch/release-v0.4.1.md +42 -0
- cairnkit-0.5.0/docs/launch/show-hn-en.md +39 -0
- cairnkit-0.5.0/docs/launch/v2ex-zh.md +38 -0
- cairnkit-0.5.0/docs/launch/x-en.md +21 -0
- cairnkit-0.5.0/docs/launch/x-zh.md +19 -0
- cairnkit-0.5.0/docs/upgrading-hermes.md +66 -0
- cairnkit-0.5.0/examples/approval-demo/index.html +79 -0
- cairnkit-0.5.0/examples/approval-demo/replay.js +87 -0
- cairnkit-0.5.0/examples/approval-demo/style.css +595 -0
- cairnkit-0.5.0/examples/approval-demo/trace.js +84 -0
- cairnkit-0.5.0/examples/approval.py +230 -0
- cairnkit-0.5.0/examples/minimal.py +61 -0
- cairnkit-0.5.0/examples/read_document.py +31 -0
- cairnkit-0.5.0/examples/verify_citations.py +109 -0
- cairnkit-0.5.0/pyproject.toml +65 -0
- cairnkit-0.5.0/reference/README.md +20 -0
- cairnkit-0.5.0/scripts/hermes_changes.py +75 -0
- cairnkit-0.5.0/scripts/render_approval_video.py +85 -0
- cairnkit-0.5.0/tests/conftest.py +12 -0
- cairnkit-0.5.0/tests/test_approval_example.py +128 -0
- cairnkit-0.5.0/tests/test_compression.py +433 -0
- cairnkit-0.5.0/tests/test_curator.py +108 -0
- cairnkit-0.5.0/tests/test_documents.py +309 -0
- cairnkit-0.5.0/tests/test_llm.py +263 -0
- cairnkit-0.5.0/tests/test_loop.py +189 -0
- cairnkit-0.5.0/tests/test_memory_tool.py +158 -0
- cairnkit-0.5.0/tests/test_misc.py +47 -0
- cairnkit-0.5.0/tests/test_search_delegate_review.py +179 -0
- cairnkit-0.5.0/tests/test_skills_agents.py +77 -0
- cairnkit-0.5.0/tests/test_stream_compress_price.py +165 -0
- cairnkit-0.5.0/uv.lock +246 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
test:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
strategy:
|
|
15
|
+
matrix:
|
|
16
|
+
python: ["3.12", "3.13"]
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
- uses: astral-sh/setup-uv@v6
|
|
20
|
+
with:
|
|
21
|
+
python-version: ${{ matrix.python }}
|
|
22
|
+
- run: uv sync --all-extras --frozen
|
|
23
|
+
- run: uv run pytest -q
|
|
24
|
+
- run: uv run ruff check .
|
|
25
|
+
- run: uv run ruff format --check .
|
|
26
|
+
- run: uv run python examples/minimal.py
|
|
27
|
+
- run: uv run python examples/approval.py
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Push a tag such as v0.5.0 to publish to PyPI. The version in pyproject.toml must match the tag.
|
|
2
|
+
# PyPI side (once): a trusted publisher for this repo, workflow release.yml, environment "pypi".
|
|
3
|
+
name: Release
|
|
4
|
+
on:
|
|
5
|
+
push:
|
|
6
|
+
tags: ["v*"]
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
jobs:
|
|
10
|
+
build:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
- uses: astral-sh/setup-uv@v6
|
|
15
|
+
with:
|
|
16
|
+
python-version: "3.12"
|
|
17
|
+
- run: uv sync --all-extras --frozen
|
|
18
|
+
- run: uv run pytest -q
|
|
19
|
+
- name: Tag matches the package version
|
|
20
|
+
run: |
|
|
21
|
+
version=$(uv run python -c 'import tomllib; print(tomllib.load(open("pyproject.toml", "rb"))["project"]["version"])')
|
|
22
|
+
test "v$version" = "$GITHUB_REF_NAME"
|
|
23
|
+
- run: uv build
|
|
24
|
+
- uses: actions/upload-artifact@v4
|
|
25
|
+
with:
|
|
26
|
+
name: dist
|
|
27
|
+
path: dist/
|
|
28
|
+
pypi:
|
|
29
|
+
needs: build
|
|
30
|
+
runs-on: ubuntu-latest
|
|
31
|
+
environment: pypi
|
|
32
|
+
permissions:
|
|
33
|
+
id-token: write
|
|
34
|
+
steps:
|
|
35
|
+
- uses: actions/download-artifact@v4
|
|
36
|
+
with:
|
|
37
|
+
name: dist
|
|
38
|
+
path: dist/
|
|
39
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
40
|
+
github-release:
|
|
41
|
+
needs: pypi
|
|
42
|
+
runs-on: ubuntu-latest
|
|
43
|
+
permissions:
|
|
44
|
+
contents: write
|
|
45
|
+
steps:
|
|
46
|
+
- uses: actions/checkout@v4
|
|
47
|
+
- uses: actions/download-artifact@v4
|
|
48
|
+
with:
|
|
49
|
+
name: dist
|
|
50
|
+
path: dist/
|
|
51
|
+
- name: Release notes from CHANGELOG.md
|
|
52
|
+
run: awk -v t="## $GITHUB_REF_NAME " '/^## /{if (p) exit; if (index($0, t) == 1) {p=1; next}} p' CHANGELOG.md > notes.md
|
|
53
|
+
- run: gh release view "$GITHUB_REF_NAME" >/dev/null 2>&1 || gh release create "$GITHUB_REF_NAME" dist/* --title "cairnkit $GITHUB_REF_NAME" --notes-file notes.md
|
|
54
|
+
env:
|
|
55
|
+
GH_TOKEN: ${{ github.token }}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Upstream Hermes source: a separate checkout, see reference/README.md
|
|
2
|
+
reference/hermes-agent/
|
|
3
|
+
|
|
4
|
+
.venv/
|
|
5
|
+
__pycache__/
|
|
6
|
+
*.py[cod]
|
|
7
|
+
.pytest_cache/
|
|
8
|
+
.ruff_cache/
|
|
9
|
+
.env
|
|
10
|
+
.env.*
|
|
11
|
+
*.sqlite*
|
|
12
|
+
*.db
|
|
13
|
+
*.log
|
|
14
|
+
.DS_Store
|
|
15
|
+
dist/
|
|
16
|
+
build/
|
|
17
|
+
*.egg-info/
|
|
18
|
+
data/
|
|
19
|
+
logs/
|
|
20
|
+
.run/
|
cairnkit-0.5.0/AGENTS.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Guidelines for contributors and AI coding assistants
|
|
2
|
+
|
|
3
|
+
Read this before changing the repository.
|
|
4
|
+
|
|
5
|
+
- **Stay independent.** Only general runtime capabilities belong here: no business code,
|
|
6
|
+
databases or web frameworks. The core depends only on the standard library and `httpx`.
|
|
7
|
+
Features that need more go in optional modules (for example `documents`, installed with
|
|
8
|
+
`cairnkit[documents]`); core modules must not import them. Storage, permissions, price
|
|
9
|
+
tables and wording come from the host through parameters, callbacks or configuration.
|
|
10
|
+
- **Identity and permissions come from the host** through `ToolContext`; the model never
|
|
11
|
+
decides who is acting. Prefer `access="propose"` for writes.
|
|
12
|
+
- **Keep Hermes' design where it fits.** Ported files name their Hermes version, commit and
|
|
13
|
+
source file in the header, and [NOTICE.md](NOTICE.md) keeps the MIT notice. Record
|
|
14
|
+
differences and their reasons in [docs/hermes-porting.md](docs/hermes-porting.md).
|
|
15
|
+
Follow [docs/upgrading-hermes.md](docs/upgrading-hermes.md) for new Hermes releases.
|
|
16
|
+
- A conversation's system prompt does not change, except through context compression.
|
|
17
|
+
- **Tests never use the network.** Use `ScriptedModel`. Real-model checks need a stated
|
|
18
|
+
purpose and the maintainer's agreement.
|
|
19
|
+
- Never commit keys, runtime data, logs or virtual environments.
|
|
20
|
+
- Adding or upgrading dependencies needs the maintainer's agreement.
|
|
21
|
+
- **Public API changes need a release**: bump the version as described in the
|
|
22
|
+
[README](README.md#versioning), update [CHANGELOG.md](CHANGELOG.md), tag the commit.
|
|
23
|
+
- Before committing: `uv run pytest -q && uv run ruff check . && uv run ruff format --check .`
|
|
24
|
+
- Keep other people's uncommitted work; stage files one by one.
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
Versioning rules: see [README](README.md#versioning).
|
|
4
|
+
|
|
5
|
+
## v0.5.0 (2026-11-09)
|
|
6
|
+
|
|
7
|
+
Follows Hermes Agent v0.23.0 (`2bd378e`). What was taken, skipped or left for later, and why:
|
|
8
|
+
[docs/hermes-porting.md](https://github.com/wasabihu/cairnkit/blob/v0.5.0/docs/hermes-porting.md#v0230-2bd378e-checked-2026-11-09).
|
|
9
|
+
|
|
10
|
+
- **On PyPI.** Install with `pip install cairnkit` or `uv add cairnkit`; releases are
|
|
11
|
+
published from a tag (`.github/workflows/release.yml`).
|
|
12
|
+
- New example `examples/verify_citations.py`: a read tool that checks a draft's DOIs and PMIDs
|
|
13
|
+
with [claimcite](https://github.com/wasabihu/claimcite) before a person reviews it.
|
|
14
|
+
|
|
15
|
+
New (optional, existing code keeps working):
|
|
16
|
+
|
|
17
|
+
- `ContextCompressor(fallback_model=...)`: when a separate summary model fails (dead
|
|
18
|
+
endpoint, quota, unknown model, bad key), the summary is retried once on this model,
|
|
19
|
+
usually the agent's main model. `CompressionResult.fallback_model_used` and
|
|
20
|
+
`summary_model_error` say it happened, so the host can warn that the summary model is broken.
|
|
21
|
+
- `ClassifiedError.context["timeout_phase"]` (`"connect"` or `"read"`), and timeout messages
|
|
22
|
+
that say whether a connection was made and what to change. `AllModelsFailed` repeats them.
|
|
23
|
+
|
|
24
|
+
Changed behaviour:
|
|
25
|
+
|
|
26
|
+
- Compression summaries have a `## Unverified / Working Hypotheses` section and the prompt asks
|
|
27
|
+
for uncertain claims as `UNVERIFIED: <claim> — <basis>` lines, so guesses no longer turn into
|
|
28
|
+
facts. Hosts with their own `summary_sections` get the label rule without the section.
|
|
29
|
+
- The lean compression tail never exceeds half the trigger, so a small `threshold_tokens` on a
|
|
30
|
+
large window can get below the trigger again.
|
|
31
|
+
- A 400 that names a request-body byte cap (Novita, Z.AI, "content-length ... bytes") is
|
|
32
|
+
`payload_too_large` and triggers compression instead of a format error.
|
|
33
|
+
- Streamed tool calls: a new id at an index already in use starts a new call only when it is
|
|
34
|
+
named and the previous call's arguments are complete. Ollama's parallel calls (all at index
|
|
35
|
+
0) are now separate calls; LM Studio, Kimi and MiniMax fragments stay one call.
|
|
36
|
+
- Tool results: a JSON result counts as failed when it has a non-empty `error`, a `status` of
|
|
37
|
+
`failed`/`error`, or a `results` list where every item has an error (before, only
|
|
38
|
+
`success: false`). This affects loop protection and `ToolRecord.failed`. A rate-limited
|
|
39
|
+
failure no longer counts toward the identical-arguments warning and block.
|
|
40
|
+
- Memory tool: the schema lists only `target` and `operations` (a single change is a one-item
|
|
41
|
+
list). The single-change shape and the `new_text` alias are still accepted. Batches that
|
|
42
|
+
only remove, or a lone duplicate add, are no longer refused when the store is over a lowered
|
|
43
|
+
limit, and a lone add over the limit says how many characters to free.
|
|
44
|
+
- `history_search` with no arguments lists 10 documents by default (a search still returns 3).
|
|
45
|
+
- `REVIEW_INSTRUCTION` tells the reviewer to save only facts grounded in the conversation, never
|
|
46
|
+
inferred traits or one-off requests as standing preferences.
|
|
47
|
+
|
|
48
|
+
## v0.4.1 (2026-10-22)
|
|
49
|
+
|
|
50
|
+
- `documents`: text and Markdown files saved as Simplified Chinese by older Windows tools
|
|
51
|
+
(GBK / GB18030) are now read correctly instead of as Windows-1252 garbage. The result
|
|
52
|
+
carries a warning saying how the text was read. Western legacy text is unchanged.
|
|
53
|
+
|
|
54
|
+
## v0.4.0 (2026-10-22)
|
|
55
|
+
|
|
56
|
+
- `make_delegate_tool(..., on_child_run=...)`: a hook that sees every finished child run
|
|
57
|
+
(agent, result, parent context), so child model calls can be billed. The parent's
|
|
58
|
+
`RunResult.usage` still covers only its own calls.
|
|
59
|
+
- `generate_title(..., on_usage=...)`: receives the title call's token usage, also when the
|
|
60
|
+
title is rejected.
|
|
61
|
+
- `RunResult.compression_usage` and `CompressionResult.usage`: what the summary model used
|
|
62
|
+
when compaction ran, so it can be billed too.
|
|
63
|
+
|
|
64
|
+
All are additions (optional arguments or new fields); existing code keeps working unchanged.
|
|
65
|
+
|
|
66
|
+
## v0.3.0 (2026-10-21)
|
|
67
|
+
|
|
68
|
+
First public release. The project was developed privately as `agentcore`.
|
|
69
|
+
|
|
70
|
+
- **Renamed to cairnkit.** The package, the import name and the repository are now
|
|
71
|
+
`cairnkit`. To upgrade from `agentcore` 0.2: change the dependency to
|
|
72
|
+
`cairnkit` (or `cairnkit[documents]`), point the source at
|
|
73
|
+
`https://github.com/wasabihu/cairnkit` tag `v0.3.0`, and replace `agentcore` with
|
|
74
|
+
`cairnkit` in imports. Nothing else changed in the API.
|
|
75
|
+
- English documentation, contribution and security guides, CI on Python 3.12 and 3.13,
|
|
76
|
+
`py.typed`, and two more examples (`approval.py`, `read_document.py`).
|
|
77
|
+
|
|
78
|
+
## v0.2.0 (2026-10-21)
|
|
79
|
+
|
|
80
|
+
- Optional module `documents`: PDF, Word (.docx), Excel (.xlsx) and text to Markdown
|
|
81
|
+
(`extract_text`), with limits on size, pages, rows and unzipped size, stable error codes
|
|
82
|
+
(`DocumentError.code`) and replaceable readers (`register_extractor`).
|
|
83
|
+
- Optional dependency group `documents` (pypdf, openpyxl). Core dependencies unchanged.
|
|
84
|
+
|
|
85
|
+
## v0.1.0 (2026-10-21)
|
|
86
|
+
|
|
87
|
+
First version as a standalone package, based on Hermes Agent v0.22.2 (`d923a5e`).
|
|
88
|
+
|
|
89
|
+
- Agent loop (streaming, tool rounds, loop protection, forced final answer)
|
|
90
|
+
- Tool registry with access levels and roles; three-layer system prompt; skills shown on
|
|
91
|
+
demand with injection scanning
|
|
92
|
+
- Memory editing rules (add / patch / remove), history search, background review,
|
|
93
|
+
delegation to sub-agents
|
|
94
|
+
- Context compression following Hermes' full implementation (token anchors, cooldowns,
|
|
95
|
+
anti-thrashing, incremental summaries, forced compaction and retry on overflow)
|
|
96
|
+
- Skill curation (returns a plan; the host decides)
|
|
97
|
+
- Models: OpenAI-compatible client, OpenRouter, error classification, fallback chain,
|
|
98
|
+
offline scripted model; cost estimates; automatic titles
|
|
99
|
+
- `scripts/hermes_changes.py` and the Hermes upgrade guide
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Thanks for helping. cairnkit is small on purpose, so please open an issue to discuss a new
|
|
4
|
+
feature before writing it. Bug fixes, tests and documentation improvements are welcome
|
|
5
|
+
directly as pull requests.
|
|
6
|
+
|
|
7
|
+
## Set up
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
git clone https://github.com/wasabihu/cairnkit
|
|
11
|
+
cd cairnkit
|
|
12
|
+
uv sync --all-extras
|
|
13
|
+
uv run pytest -q && uv run ruff check . && uv run ruff format --check .
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## What a good change looks like
|
|
17
|
+
|
|
18
|
+
- It follows [AGENTS.md](AGENTS.md): the core stays dependency-light, identity comes from
|
|
19
|
+
the host, and tests run offline with `ScriptedModel`.
|
|
20
|
+
- It has tests for the behaviour it adds or fixes.
|
|
21
|
+
- Public API changes are described in [CHANGELOG.md](CHANGELOG.md) under an "Unreleased"
|
|
22
|
+
heading.
|
|
23
|
+
- Code ported from Hermes Agent names its source in the file header.
|
|
24
|
+
|
|
25
|
+
## Reporting bugs
|
|
26
|
+
|
|
27
|
+
Include the cairnkit version, Python version, a minimal script that shows the problem
|
|
28
|
+
(with `ScriptedModel` if possible) and what you expected. For security problems, see
|
|
29
|
+
[SECURITY.md](SECURITY.md) instead of opening a public issue.
|
|
30
|
+
|
|
31
|
+
By contributing you agree that your contribution is licensed under the MIT License.
|
cairnkit-0.5.0/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 wasabihu
|
|
4
|
+
Copyright (c) 2025 Nous Research (portions adapted from Hermes Agent, see NOTICE.md)
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
cairnkit-0.5.0/NOTICE.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Notice
|
|
2
|
+
|
|
3
|
+
cairnkit ports the design and several algorithms of **Hermes Agent v0.23.0**
|
|
4
|
+
(<https://github.com/NousResearch/hermes-agent>, commit `2bd378e`).
|
|
5
|
+
|
|
6
|
+
```
|
|
7
|
+
MIT License
|
|
8
|
+
|
|
9
|
+
Copyright (c) 2025 Nous Research
|
|
10
|
+
|
|
11
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
12
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
13
|
+
in the Software without restriction, including without limitation the rights
|
|
14
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
15
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
16
|
+
furnished to do so, subject to the following conditions:
|
|
17
|
+
|
|
18
|
+
The above copyright notice and this permission notice shall be included in all
|
|
19
|
+
copies or substantial portions of the Software.
|
|
20
|
+
|
|
21
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
22
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
23
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
24
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
25
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
26
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
27
|
+
SOFTWARE.
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Files that contain ported or adapted code say so in their header, naming the
|
|
31
|
+
Hermes source file.
|
cairnkit-0.5.0/PKG-INFO
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: cairnkit
|
|
3
|
+
Version: 0.5.0
|
|
4
|
+
Summary: A small agent runtime for business apps: the host owns identity, writes become drafts a person approves.
|
|
5
|
+
Project-URL: Homepage, https://github.com/wasabihu/cairnkit
|
|
6
|
+
Project-URL: Repository, https://github.com/wasabihu/cairnkit
|
|
7
|
+
Project-URL: Issues, https://github.com/wasabihu/cairnkit/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/wasabihu/cairnkit/blob/main/CHANGELOG.md
|
|
9
|
+
Author-email: wasabihu <wasabi.net@gmail.com>
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
License-File: NOTICE.md
|
|
13
|
+
Keywords: agent-runtime,agents,ai,context-compression,hermes-agent,human-in-the-loop,llm,memory
|
|
14
|
+
Classifier: Development Status :: 3 - Alpha
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Python: >=3.12
|
|
24
|
+
Requires-Dist: httpx<1,>=0.27
|
|
25
|
+
Provides-Extra: documents
|
|
26
|
+
Requires-Dist: openpyxl<4,>=3.1; extra == 'documents'
|
|
27
|
+
Requires-Dist: pypdf<7,>=6; extra == 'documents'
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
# cairnkit
|
|
31
|
+
|
|
32
|
+
[](https://github.com/wasabihu/cairnkit/actions/workflows/ci.yml)
|
|
33
|
+
[](LICENSE)
|
|
34
|
+

|
|
35
|
+
|
|
36
|
+
**A small agent runtime for business applications.** Give an agent useful tools while
|
|
37
|
+
keeping identity, permissions and persistence in your application. Use proposal tools
|
|
38
|
+
and a host-controlled review step when a person should decide what gets saved.
|
|
39
|
+
|
|
40
|
+
A cairn is a pile of stones that marks a trail so the next walker can follow it. cairnkit
|
|
41
|
+
does the same for agents: approved memory, searchable history and curated skills that the
|
|
42
|
+
next conversation builds on.
|
|
43
|
+
|
|
44
|
+
[中文说明](README.zh-CN.md)
|
|
45
|
+
|
|
46
|
+
## See the approval boundary
|
|
47
|
+
|
|
48
|
+
A fictional support ticket asks to change an invoice email. The agent reads it and
|
|
49
|
+
creates a draft. The saved record changes only after the host's reviewer approves it.
|
|
50
|
+
Rejecting the draft leaves the record untouched.
|
|
51
|
+
|
|
52
|
+

|
|
53
|
+
|
|
54
|
+
[Watch the 35-second walkthrough](docs/assets/cairnkit-approval-demo.mp4) ·
|
|
55
|
+
[Run the example and choose approve or reject](docs/approval-demo.md)
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
git clone https://github.com/wasabihu/cairnkit.git
|
|
59
|
+
cd cairnkit
|
|
60
|
+
uv run python examples/approval.py
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**Offline, fictional data, no API key.** The model uses predetermined `ScriptedModel`
|
|
64
|
+
responses. The video replays snapshots from a real execution of that example, including
|
|
65
|
+
a scripted approval choice. It demonstrates the host integration, not model quality.
|
|
66
|
+
The in-memory review function is example application code, not a built-in approval service.
|
|
67
|
+
|
|
68
|
+
## Why another agent runtime
|
|
69
|
+
|
|
70
|
+
In a business application, several people may share records, each with different roles.
|
|
71
|
+
The application needs to retain control over which tools an agent can use and when a
|
|
72
|
+
proposed change becomes a saved record. cairnkit provides building blocks for that:
|
|
73
|
+
|
|
74
|
+
- **Identity comes from the host, never from the model.** Tools receive the signed-in
|
|
75
|
+
person, their roles and the record being worked on in a `ToolContext`.
|
|
76
|
+
- **Make changes reviewable.** Tools declare an access level (`read`, `propose`, `write`)
|
|
77
|
+
and allowed roles. Your proposal handler creates drafts; your application implements
|
|
78
|
+
approval and persistence. The access label alone does not enforce human review.
|
|
79
|
+
- **Memory with guard rails.** Add / patch / remove edits located by quoted text, size
|
|
80
|
+
limits, prompt-injection scanning and frozen snapshots, ported from Hermes Agent.
|
|
81
|
+
- **Compact long conversations.** A port of Hermes Agent's context compression:
|
|
82
|
+
incremental structured summaries, real token anchors, cooldowns and verbatim user quotes.
|
|
83
|
+
- **Small.** The core depends only on the standard library and `httpx`. No web framework,
|
|
84
|
+
no database, no vector store: storage and permissions stay in your application.
|
|
85
|
+
|
|
86
|
+
## What is inside
|
|
87
|
+
|
|
88
|
+
| Area | Module | What it does |
|
|
89
|
+
| -------------------- | ---------------------------- | -------------------------------------------------------------------------------------------------------------- |
|
|
90
|
+
| Agent loop | `loop.py` | Model → tools → model, streaming, a forced plain answer when the step budget runs out |
|
|
91
|
+
| Tools | `tools.py` | Schema, handler, access level, allowed roles; identity is injected, never taken from the model |
|
|
92
|
+
| Loop protection | `guardrails.py` | Warns or stops on repeated failing calls and identical results |
|
|
93
|
+
| System prompt | `prompt.py` | Stable / context / volatile layers, assembled once per conversation for prompt caching |
|
|
94
|
+
| Memory | `memory.py`, `memory_ops.py` | Hermes' add / patch / remove rules; your store decides whether changes need approval |
|
|
95
|
+
| History search | `search.py` | One tool, four modes (search, read, context, browse); no model calls |
|
|
96
|
+
| Skills | `skills.py` | `SKILL.md` folders, shown on demand, scanned for injection when loaded |
|
|
97
|
+
| Context compression | `compression.py` | Four-phase compaction with incremental summaries and per-conversation `CompressionState` |
|
|
98
|
+
| Background review | `review.py` | After a turn, a forked agent decides whether anything should be remembered |
|
|
99
|
+
| Delegation | `delegate.py` | Hand a focused task to a specialist agent and get back only its answer (one level deep) |
|
|
100
|
+
| Skill curation | `curator.py` | Marks unused skills stale, suggests archives and merges; returns a plan, you decide |
|
|
101
|
+
| Injection scanning | `security.py` | Prompt injection, hidden characters and exfiltration patterns |
|
|
102
|
+
| Titles | `titles.py` | Instant title, upgraded by a small model; never overrides a title the user typed |
|
|
103
|
+
| Models | `llm/` | OpenAI-compatible client (streaming), OpenRouter, error classification, fallback chain, offline scripted model |
|
|
104
|
+
| Cost | `pricing.py` | Uses the provider's reported cost, otherwise a price table |
|
|
105
|
+
| Agent definitions | `agents.py` | One folder per agent: `agent.toml` + `SOUL.md` |
|
|
106
|
+
| Documents (optional) | `documents.py` | PDF, Word, Excel and text to Markdown, with size and zip-bomb limits |
|
|
107
|
+
|
|
108
|
+
## Install
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
pip install cairnkit
|
|
112
|
+
# or
|
|
113
|
+
uv add cairnkit
|
|
114
|
+
# with document reading (adds pypdf and openpyxl):
|
|
115
|
+
pip install "cairnkit[documents]" # or: uv add "cairnkit[documents]"
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Python 3.12 or newer.
|
|
119
|
+
|
|
120
|
+
## Quick start
|
|
121
|
+
|
|
122
|
+
An agent is a folder:
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
agents/
|
|
126
|
+
greeter/
|
|
127
|
+
agent.toml
|
|
128
|
+
SOUL.md
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
```toml
|
|
132
|
+
# agents/greeter/agent.toml
|
|
133
|
+
description = "Answers questions about opening hours."
|
|
134
|
+
model = "default" # a name your application maps to a model
|
|
135
|
+
tools = ["opening_hours"] # the only tools this agent may use
|
|
136
|
+
skills = [] # optional: skills from a skills/ folder
|
|
137
|
+
max_iterations = 4
|
|
138
|
+
temperature = 0.2
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
`SOUL.md` holds the persona and rules; it goes into the stable layer of the system prompt.
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
from cairnkit import Agent, ToolContext, ToolRegistry, load_agents, openrouter, tool_result
|
|
145
|
+
|
|
146
|
+
tools = ToolRegistry()
|
|
147
|
+
|
|
148
|
+
@tools.tool(name="opening_hours", description="Opening hours for a weekday.",
|
|
149
|
+
parameters={"type": "object", "properties": {"day": {"type": "string"}},
|
|
150
|
+
"required": ["day"]})
|
|
151
|
+
def opening_hours(args, ctx):
|
|
152
|
+
return tool_result(day=args["day"], hours="7am-7pm")
|
|
153
|
+
|
|
154
|
+
specs = load_agents("agents")
|
|
155
|
+
agent = Agent(specs["greeter"], model=openrouter("openai/gpt-4.1-mini", api_key="..."),
|
|
156
|
+
tools=tools)
|
|
157
|
+
result = await agent.run("When do you open on Sunday?", ToolContext(principal={"id": "u1"}))
|
|
158
|
+
print(result.text)
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Runnable examples, no API key needed:
|
|
162
|
+
|
|
163
|
+
| Example | Shows |
|
|
164
|
+
| ------------------------------------------------------ | ---------------------------------------------------------- |
|
|
165
|
+
| [examples/minimal.py](examples/minimal.py) | The smallest complete agent with one tool |
|
|
166
|
+
| [examples/approval.py](examples/approval.py) | Read a ticket, propose a change, approve or reject in the host |
|
|
167
|
+
| [examples/read_document.py](examples/read_document.py) | Reading a PDF, Word or Excel file into Markdown |
|
|
168
|
+
| [examples/verify_citations.py](examples/verify_citations.py) | A read tool that checks a draft's DOIs and PMIDs with [claimcite](https://github.com/wasabihu/claimcite) before review (needs network: `uv run --with claimcite python examples/verify_citations.py`) |
|
|
169
|
+
|
|
170
|
+
## Rules worth keeping
|
|
171
|
+
|
|
172
|
+
- **Identity is not the model's to decide.** Put the user and the record in `ToolContext`.
|
|
173
|
+
- **Assemble the system prompt once per conversation.** Pass `RunResult.system_prompt` back
|
|
174
|
+
as `run(system_prompt=...)` on later turns so the provider's prompt cache keeps working.
|
|
175
|
+
- **Prefer `access="propose"`** for anything that changes data, and let a person approve.
|
|
176
|
+
- **Test offline** with `ScriptedModel`; automated tests should never call a real model.
|
|
177
|
+
- **Text from users, files and tools is data.** Keep it out of the system prompt.
|
|
178
|
+
|
|
179
|
+
## Reading documents (optional)
|
|
180
|
+
|
|
181
|
+
```python
|
|
182
|
+
from cairnkit.documents import DocumentError, Limits, extract_text
|
|
183
|
+
|
|
184
|
+
try:
|
|
185
|
+
doc = extract_text(data, "price-list.xlsx", limits=Limits(max_chars=40_000))
|
|
186
|
+
except DocumentError as exc:
|
|
187
|
+
exc.code # too_large, empty, unsupported, old_format, image, encrypted, no_text, unreadable, needs_extra
|
|
188
|
+
else:
|
|
189
|
+
doc.text, doc.kind, doc.parts, doc.truncated, doc.warnings
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
- **Output** is Markdown. Word keeps headings, lists and tables; Excel becomes one table per
|
|
193
|
+
visible sheet (dates as `2026-11-07`); PDF text is marked `[Page N]`.
|
|
194
|
+
- **The format is judged by content**, not only the file name. Old `.doc`/`.xls`, pictures
|
|
195
|
+
and scanned PDFs get their own error codes so your UI can explain what to do.
|
|
196
|
+
- **Limits** on file size, PDF pages, Excel rows and unzipped size. Only text is read;
|
|
197
|
+
macros and formulas never run.
|
|
198
|
+
- **Replaceable**: `register_extractor("pdf", fn)` swaps in another reader (MarkItDown,
|
|
199
|
+
Docling, an OCR service) without changing callers.
|
|
200
|
+
|
|
201
|
+
## Relationship to Hermes Agent
|
|
202
|
+
|
|
203
|
+
Many mechanisms and algorithms are ported from
|
|
204
|
+
[Hermes Agent](https://github.com/NousResearch/hermes-agent) v0.23.0 (MIT, Nous Research):
|
|
205
|
+
the prompt layers, the tool loop, memory editing rules, history search, skills, context
|
|
206
|
+
compression, background review, curation and delegation. Every ported file names its
|
|
207
|
+
Hermes source in its header. Hermes is a personal assistant with files, terminals and
|
|
208
|
+
dozens of platforms; cairnkit keeps the algorithms and replaces storage, configuration and
|
|
209
|
+
permissions so they fit a multi-user web application. See
|
|
210
|
+
[docs/hermes-porting.md](docs/hermes-porting.md) for the map and
|
|
211
|
+
[docs/upgrading-hermes.md](docs/upgrading-hermes.md) for following new Hermes releases.
|
|
212
|
+
cairnkit is not affiliated with or endorsed by Nous Research.
|
|
213
|
+
|
|
214
|
+
## Versioning
|
|
215
|
+
|
|
216
|
+
`MAJOR.MINOR.PATCH`. Before 1.0:
|
|
217
|
+
|
|
218
|
+
- **Patch** (0.3.0 → 0.3.1): fixes, tests and docs; upgrade freely.
|
|
219
|
+
- **Minor** (0.3 → 0.4): new features, Hermes updates, or anything that needs changes in
|
|
220
|
+
your code. [CHANGELOG.md](CHANGELOG.md) says what to change.
|
|
221
|
+
|
|
222
|
+
## Development
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
uv sync --all-extras
|
|
226
|
+
uv run pytest -q && uv run ruff check . && uv run ruff format --check .
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md). Security reports: [SECURITY.md](SECURITY.md).
|
|
230
|
+
|
|
231
|
+
## License
|
|
232
|
+
|
|
233
|
+
MIT, see [LICENSE](LICENSE). Ported Hermes Agent code keeps its MIT notice in
|
|
234
|
+
[NOTICE.md](NOTICE.md).
|