cairnkit 0.5.0__py3-none-any.whl
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/__init__.py +143 -0
- cairnkit/agents.py +111 -0
- cairnkit/compression.py +973 -0
- cairnkit/curator.py +217 -0
- cairnkit/delegate.py +99 -0
- cairnkit/documents.py +421 -0
- cairnkit/guardrails.py +124 -0
- cairnkit/llm/__init__.py +17 -0
- cairnkit/llm/base.py +34 -0
- cairnkit/llm/errors.py +185 -0
- cairnkit/llm/fallback.py +107 -0
- cairnkit/llm/openai_compat.py +281 -0
- cairnkit/llm/scripted.py +54 -0
- cairnkit/loop.py +310 -0
- cairnkit/memory.py +185 -0
- cairnkit/memory_ops.py +201 -0
- cairnkit/pricing.py +37 -0
- cairnkit/prompt.py +27 -0
- cairnkit/py.typed +0 -0
- cairnkit/review.py +55 -0
- cairnkit/search.py +130 -0
- cairnkit/security.py +215 -0
- cairnkit/skills.py +184 -0
- cairnkit/titles.py +75 -0
- cairnkit/tools.py +175 -0
- cairnkit/types.py +58 -0
- cairnkit-0.5.0.dist-info/METADATA +234 -0
- cairnkit-0.5.0.dist-info/RECORD +31 -0
- cairnkit-0.5.0.dist-info/WHEEL +4 -0
- cairnkit-0.5.0.dist-info/licenses/LICENSE +22 -0
- cairnkit-0.5.0.dist-info/licenses/NOTICE.md +31 -0
cairnkit/__init__.py
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"""cairnkit: a small agent runtime for business applications.
|
|
2
|
+
|
|
3
|
+
Design and several algorithms are ported from Hermes Agent v0.23.0 (MIT, Nous Research);
|
|
4
|
+
see NOTICE.md. Public API:
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from cairnkit.agents import AgentConfigError, AgentSpec, load_agent, load_agents, validate_persona
|
|
8
|
+
from cairnkit.compression import (
|
|
9
|
+
LATEST_MESSAGE_MARKER,
|
|
10
|
+
SUMMARY_PREFIX,
|
|
11
|
+
CompressionConfig,
|
|
12
|
+
CompressionResult,
|
|
13
|
+
CompressionState,
|
|
14
|
+
ContextCompressor,
|
|
15
|
+
estimate_tokens,
|
|
16
|
+
)
|
|
17
|
+
from cairnkit.curator import (
|
|
18
|
+
CuratorPolicy,
|
|
19
|
+
CuratorReport,
|
|
20
|
+
SkillActivity,
|
|
21
|
+
Suggestion,
|
|
22
|
+
Transition,
|
|
23
|
+
plan_transitions,
|
|
24
|
+
should_run,
|
|
25
|
+
suggest_consolidation,
|
|
26
|
+
)
|
|
27
|
+
from cairnkit.delegate import make_delegate_tool
|
|
28
|
+
from cairnkit.guardrails import GuardrailConfig
|
|
29
|
+
from cairnkit.llm import (
|
|
30
|
+
ChatModel,
|
|
31
|
+
ClassifiedError,
|
|
32
|
+
FailoverReason,
|
|
33
|
+
FallbackModel,
|
|
34
|
+
ModelAPIError,
|
|
35
|
+
OpenAICompatModel,
|
|
36
|
+
ScriptedModel,
|
|
37
|
+
classify_error,
|
|
38
|
+
openrouter,
|
|
39
|
+
)
|
|
40
|
+
from cairnkit.loop import Agent, RunEvent, RunResult, ToolRecord
|
|
41
|
+
from cairnkit.memory import (
|
|
42
|
+
MemoryItem,
|
|
43
|
+
MemoryStore,
|
|
44
|
+
make_memory_tool,
|
|
45
|
+
render_snapshot,
|
|
46
|
+
screen_memory_write,
|
|
47
|
+
)
|
|
48
|
+
from cairnkit.memory_ops import MemoryChange, MemoryPlan, MemoryPlanError, plan_operations
|
|
49
|
+
from cairnkit.pricing import Price, cost_of, estimate_cost
|
|
50
|
+
from cairnkit.prompt import PromptParts, build_system_prompt
|
|
51
|
+
from cairnkit.review import REVIEW_INSTRUCTION, review_conversation
|
|
52
|
+
from cairnkit.search import SearchBackend, SearchHit, SearchItem, make_search_tool
|
|
53
|
+
from cairnkit.security import first_threat_message, scan_for_threats
|
|
54
|
+
from cairnkit.skills import Skill, SkillError, SkillLibrary, make_skill
|
|
55
|
+
from cairnkit.titles import can_replace, derive_title, generate_title
|
|
56
|
+
from cairnkit.tools import (
|
|
57
|
+
ToolContext,
|
|
58
|
+
ToolError,
|
|
59
|
+
ToolOutcome,
|
|
60
|
+
ToolRegistry,
|
|
61
|
+
ToolSpec,
|
|
62
|
+
tool_error,
|
|
63
|
+
tool_result,
|
|
64
|
+
)
|
|
65
|
+
from cairnkit.types import Message, ModelResponse, ToolCall, Usage
|
|
66
|
+
|
|
67
|
+
__all__ = [
|
|
68
|
+
"Agent",
|
|
69
|
+
"AgentConfigError",
|
|
70
|
+
"AgentSpec",
|
|
71
|
+
"build_system_prompt",
|
|
72
|
+
"can_replace",
|
|
73
|
+
"ChatModel",
|
|
74
|
+
"ClassifiedError",
|
|
75
|
+
"classify_error",
|
|
76
|
+
"CompressionConfig",
|
|
77
|
+
"CompressionResult",
|
|
78
|
+
"CompressionState",
|
|
79
|
+
"ContextCompressor",
|
|
80
|
+
"CuratorPolicy",
|
|
81
|
+
"CuratorReport",
|
|
82
|
+
"SkillActivity",
|
|
83
|
+
"Suggestion",
|
|
84
|
+
"Transition",
|
|
85
|
+
"plan_transitions",
|
|
86
|
+
"should_run",
|
|
87
|
+
"suggest_consolidation",
|
|
88
|
+
"make_skill",
|
|
89
|
+
"validate_persona",
|
|
90
|
+
"LATEST_MESSAGE_MARKER",
|
|
91
|
+
"cost_of",
|
|
92
|
+
"derive_title",
|
|
93
|
+
"estimate_cost",
|
|
94
|
+
"estimate_tokens",
|
|
95
|
+
"FailoverReason",
|
|
96
|
+
"FallbackModel",
|
|
97
|
+
"first_threat_message",
|
|
98
|
+
"generate_title",
|
|
99
|
+
"GuardrailConfig",
|
|
100
|
+
"load_agent",
|
|
101
|
+
"load_agents",
|
|
102
|
+
"make_delegate_tool",
|
|
103
|
+
"make_memory_tool",
|
|
104
|
+
"make_search_tool",
|
|
105
|
+
"MemoryChange",
|
|
106
|
+
"MemoryItem",
|
|
107
|
+
"MemoryPlan",
|
|
108
|
+
"MemoryPlanError",
|
|
109
|
+
"MemoryStore",
|
|
110
|
+
"Message",
|
|
111
|
+
"ModelAPIError",
|
|
112
|
+
"ModelResponse",
|
|
113
|
+
"OpenAICompatModel",
|
|
114
|
+
"openrouter",
|
|
115
|
+
"plan_operations",
|
|
116
|
+
"Price",
|
|
117
|
+
"PromptParts",
|
|
118
|
+
"render_snapshot",
|
|
119
|
+
"review_conversation",
|
|
120
|
+
"REVIEW_INSTRUCTION",
|
|
121
|
+
"RunEvent",
|
|
122
|
+
"RunResult",
|
|
123
|
+
"scan_for_threats",
|
|
124
|
+
"screen_memory_write",
|
|
125
|
+
"ScriptedModel",
|
|
126
|
+
"SearchBackend",
|
|
127
|
+
"SearchHit",
|
|
128
|
+
"SearchItem",
|
|
129
|
+
"Skill",
|
|
130
|
+
"SkillError",
|
|
131
|
+
"SkillLibrary",
|
|
132
|
+
"SUMMARY_PREFIX",
|
|
133
|
+
"tool_error",
|
|
134
|
+
"tool_result",
|
|
135
|
+
"ToolCall",
|
|
136
|
+
"ToolContext",
|
|
137
|
+
"ToolError",
|
|
138
|
+
"ToolOutcome",
|
|
139
|
+
"ToolRecord",
|
|
140
|
+
"ToolRegistry",
|
|
141
|
+
"ToolSpec",
|
|
142
|
+
"Usage",
|
|
143
|
+
]
|
cairnkit/agents.py
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""Agent definitions loaded from a directory.
|
|
2
|
+
|
|
3
|
+
agents/
|
|
4
|
+
note_structurer/
|
|
5
|
+
agent.toml # name, description, tools, skills, limits
|
|
6
|
+
SOUL.md # persona and rules (stable prompt tier)
|
|
7
|
+
|
|
8
|
+
Loading a directory is all a project needs to declare its agents; code only
|
|
9
|
+
supplies the tools and the model.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import tomllib
|
|
15
|
+
from dataclasses import dataclass, field
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
from cairnkit.guardrails import GuardrailConfig
|
|
19
|
+
from cairnkit.security import first_threat_message
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class AgentConfigError(ValueError):
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass(frozen=True)
|
|
27
|
+
class AgentSpec:
|
|
28
|
+
name: str
|
|
29
|
+
description: str
|
|
30
|
+
persona: str
|
|
31
|
+
model: str = "default" # key the host maps to a ChatModel
|
|
32
|
+
tools: list[str] = field(default_factory=list)
|
|
33
|
+
skills: list[str] = field(default_factory=list)
|
|
34
|
+
max_iterations: int = 6
|
|
35
|
+
temperature: float | None = None
|
|
36
|
+
max_tokens: int | None = None
|
|
37
|
+
guardrails: GuardrailConfig = field(default_factory=GuardrailConfig)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
_ALLOWED_KEYS = {
|
|
41
|
+
"name",
|
|
42
|
+
"description",
|
|
43
|
+
"model",
|
|
44
|
+
"tools",
|
|
45
|
+
"skills",
|
|
46
|
+
"max_iterations",
|
|
47
|
+
"temperature",
|
|
48
|
+
"max_tokens",
|
|
49
|
+
"guardrails",
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
MAX_PERSONA_CHARS = 12_000
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def validate_persona(persona: str) -> str:
|
|
57
|
+
"""Rules for a persona edited outside the repository (for example in an admin page)."""
|
|
58
|
+
persona = persona.strip()
|
|
59
|
+
if not persona:
|
|
60
|
+
raise AgentConfigError("the persona is empty")
|
|
61
|
+
if len(persona) > MAX_PERSONA_CHARS:
|
|
62
|
+
raise AgentConfigError(f"the persona may hold at most {MAX_PERSONA_CHARS} characters")
|
|
63
|
+
if threat := first_threat_message(persona, scope="all"):
|
|
64
|
+
raise AgentConfigError(threat)
|
|
65
|
+
return persona
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def load_agent(directory: str | Path) -> AgentSpec:
|
|
69
|
+
directory = Path(directory)
|
|
70
|
+
config_path = directory / "agent.toml"
|
|
71
|
+
try:
|
|
72
|
+
data = tomllib.loads(config_path.read_text(encoding="utf-8"))
|
|
73
|
+
except (OSError, tomllib.TOMLDecodeError) as exc:
|
|
74
|
+
raise AgentConfigError(f"{config_path}: {exc}") from exc
|
|
75
|
+
unknown = set(data) - _ALLOWED_KEYS
|
|
76
|
+
if unknown:
|
|
77
|
+
raise AgentConfigError(f"{config_path}: unknown keys {sorted(unknown)}")
|
|
78
|
+
name = data.get("name", directory.name)
|
|
79
|
+
if name != directory.name:
|
|
80
|
+
raise AgentConfigError(f"{config_path}: name {name!r} must match its directory")
|
|
81
|
+
soul = directory / "SOUL.md"
|
|
82
|
+
persona = soul.read_text(encoding="utf-8").strip() if soul.is_file() else ""
|
|
83
|
+
if not persona:
|
|
84
|
+
raise AgentConfigError(f"{directory}: SOUL.md is missing or empty")
|
|
85
|
+
if threat := first_threat_message(persona, scope="all"):
|
|
86
|
+
raise AgentConfigError(f"{soul}: {threat}")
|
|
87
|
+
max_iterations = int(data.get("max_iterations", 6))
|
|
88
|
+
if not 1 <= max_iterations <= 50:
|
|
89
|
+
raise AgentConfigError(f"{config_path}: max_iterations must be 1-50")
|
|
90
|
+
return AgentSpec(
|
|
91
|
+
name=name,
|
|
92
|
+
description=str(data.get("description", "")),
|
|
93
|
+
persona=persona,
|
|
94
|
+
model=str(data.get("model", "default")),
|
|
95
|
+
tools=list(data.get("tools", [])),
|
|
96
|
+
skills=list(data.get("skills", [])),
|
|
97
|
+
max_iterations=max_iterations,
|
|
98
|
+
temperature=data.get("temperature"),
|
|
99
|
+
max_tokens=data.get("max_tokens"),
|
|
100
|
+
guardrails=GuardrailConfig(**data.get("guardrails", {})),
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def load_agents(root: str | Path) -> dict[str, AgentSpec]:
|
|
105
|
+
root = Path(root)
|
|
106
|
+
if not root.is_dir():
|
|
107
|
+
raise AgentConfigError(f"agents directory not found: {root}")
|
|
108
|
+
return {
|
|
109
|
+
spec.name: spec
|
|
110
|
+
for spec in (load_agent(d) for d in sorted(root.iterdir()) if (d / "agent.toml").is_file())
|
|
111
|
+
}
|