krons 0.2.1__py3-none-any.whl → 0.2.2__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.
- krons/core/__init__.py +3 -0
- krons/core/base/__init__.py +4 -0
- krons/core/base/log.py +32 -0
- krons/session/session.py +114 -1
- {krons-0.2.1.dist-info → krons-0.2.2.dist-info}/METADATA +1 -1
- {krons-0.2.1.dist-info → krons-0.2.2.dist-info}/RECORD +8 -7
- {krons-0.2.1.dist-info → krons-0.2.2.dist-info}/WHEEL +0 -0
- {krons-0.2.1.dist-info → krons-0.2.2.dist-info}/licenses/LICENSE +0 -0
krons/core/__init__.py
CHANGED
|
@@ -21,6 +21,7 @@ _LAZY_IMPORTS: dict[str, tuple[str, str]] = {
|
|
|
21
21
|
"PERSISTABLE_NODE_REGISTRY": ("krons.core.base", "PERSISTABLE_NODE_REGISTRY"),
|
|
22
22
|
# Classes
|
|
23
23
|
"Broadcaster": ("krons.core.base", "Broadcaster"),
|
|
24
|
+
"DataLoggerConfig": ("krons.core.base", "DataLoggerConfig"),
|
|
24
25
|
"Edge": ("krons.core.base", "Edge"),
|
|
25
26
|
"EdgeCondition": ("krons.core.base", "EdgeCondition"),
|
|
26
27
|
"Element": ("krons.core.base", "Element"),
|
|
@@ -75,6 +76,7 @@ if TYPE_CHECKING:
|
|
|
75
76
|
NODE_REGISTRY,
|
|
76
77
|
PERSISTABLE_NODE_REGISTRY,
|
|
77
78
|
Broadcaster,
|
|
79
|
+
DataLoggerConfig,
|
|
78
80
|
Edge,
|
|
79
81
|
EdgeCondition,
|
|
80
82
|
Element,
|
|
@@ -103,6 +105,7 @@ __all__ = [
|
|
|
103
105
|
"PERSISTABLE_NODE_REGISTRY",
|
|
104
106
|
# classes
|
|
105
107
|
"Broadcaster",
|
|
108
|
+
"DataLoggerConfig",
|
|
106
109
|
"Edge",
|
|
107
110
|
"EdgeCondition",
|
|
108
111
|
"Element",
|
krons/core/base/__init__.py
CHANGED
|
@@ -26,6 +26,8 @@ _LAZY_IMPORTS: dict[str, tuple[str, str]] = {
|
|
|
26
26
|
"Edge": ("krons.core.base.graph", "Edge"),
|
|
27
27
|
"EdgeCondition": ("krons.core.base.graph", "EdgeCondition"),
|
|
28
28
|
"Graph": ("krons.core.base.graph", "Graph"),
|
|
29
|
+
# log
|
|
30
|
+
"DataLoggerConfig": ("krons.core.base.log", "DataLoggerConfig"),
|
|
29
31
|
# node
|
|
30
32
|
"NODE_REGISTRY": ("krons.core.base.node", "NODE_REGISTRY"),
|
|
31
33
|
"PERSISTABLE_NODE_REGISTRY": ("krons.core.base.node", "PERSISTABLE_NODE_REGISTRY"),
|
|
@@ -77,6 +79,7 @@ if TYPE_CHECKING:
|
|
|
77
79
|
from .eventbus import EventBus, Handler
|
|
78
80
|
from .flow import Flow
|
|
79
81
|
from .graph import Edge, EdgeCondition, Graph
|
|
82
|
+
from .log import DataLoggerConfig
|
|
80
83
|
from .node import (
|
|
81
84
|
NODE_REGISTRY,
|
|
82
85
|
PERSISTABLE_NODE_REGISTRY,
|
|
@@ -97,6 +100,7 @@ __all__ = [
|
|
|
97
100
|
"PERSISTABLE_NODE_REGISTRY",
|
|
98
101
|
# classes
|
|
99
102
|
"Broadcaster",
|
|
103
|
+
"DataLoggerConfig",
|
|
100
104
|
"Edge",
|
|
101
105
|
"EdgeCondition",
|
|
102
106
|
"Element",
|
krons/core/base/log.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Copyright (c) 2025 - 2026, HaiyangLi <quantocean.li at gmail dot com>
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
"""Logging configuration for Session message persistence.
|
|
5
|
+
|
|
6
|
+
Provides DataLoggerConfig for configuring automatic message dumps.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Literal
|
|
13
|
+
|
|
14
|
+
from pydantic import Field
|
|
15
|
+
|
|
16
|
+
from krons.core.types import HashableModel
|
|
17
|
+
|
|
18
|
+
__all__ = ("DataLoggerConfig",)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class DataLoggerConfig(HashableModel):
|
|
22
|
+
"""Configuration for Session message persistence.
|
|
23
|
+
|
|
24
|
+
Attributes:
|
|
25
|
+
persist_dir: Directory for dump files.
|
|
26
|
+
extension: Output format (.json array or .jsonl newline-delimited).
|
|
27
|
+
auto_save_on_exit: Register atexit handler on Session creation.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
persist_dir: str | Path = Field(default="./logs")
|
|
31
|
+
extension: Literal[".json", ".jsonl"] = Field(default=".jsonl")
|
|
32
|
+
auto_save_on_exit: bool = Field(default=True)
|
krons/session/session.py
CHANGED
|
@@ -9,12 +9,16 @@ Branch is a named message progression with capability/resource access control.
|
|
|
9
9
|
|
|
10
10
|
from __future__ import annotations
|
|
11
11
|
|
|
12
|
+
import atexit
|
|
12
13
|
import contextlib
|
|
13
14
|
from collections.abc import AsyncGenerator, Iterable
|
|
15
|
+
from pathlib import Path
|
|
14
16
|
from typing import Any, Literal
|
|
17
|
+
|
|
18
|
+
from krons.core.base.log import DataLoggerConfig
|
|
15
19
|
from uuid import UUID
|
|
16
20
|
|
|
17
|
-
from pydantic import Field, model_validator
|
|
21
|
+
from pydantic import Field, PrivateAttr, model_validator
|
|
18
22
|
|
|
19
23
|
from krons.core import Element, Flow, Pile, Progression
|
|
20
24
|
from krons.core.types import HashableModel, Unset, UnsetType, not_sentinel
|
|
@@ -59,6 +63,10 @@ class SessionConfig(HashableModel):
|
|
|
59
63
|
default_gen_model: str | None = None
|
|
60
64
|
default_parse_model: str | None = None
|
|
61
65
|
auto_create_default_branch: bool = True
|
|
66
|
+
log_config: DataLoggerConfig | None = Field(
|
|
67
|
+
default=None,
|
|
68
|
+
description="DataLoggerConfig for auto-logging (None = disabled)",
|
|
69
|
+
)
|
|
62
70
|
|
|
63
71
|
|
|
64
72
|
class Session(Element):
|
|
@@ -71,6 +79,9 @@ class Session(Element):
|
|
|
71
79
|
config: SessionConfig = Field(default_factory=SessionConfig)
|
|
72
80
|
default_branch_id: UUID | None = None
|
|
73
81
|
|
|
82
|
+
_registered_atexit: bool = PrivateAttr(default=False)
|
|
83
|
+
_dump_count: int = PrivateAttr(default=0)
|
|
84
|
+
|
|
74
85
|
@model_validator(mode="after")
|
|
75
86
|
def _validate_default_branch(self) -> Session:
|
|
76
87
|
"""Auto-create default branch and register built-in operations."""
|
|
@@ -83,6 +94,15 @@ class Session(Element):
|
|
|
83
94
|
)
|
|
84
95
|
self.set_default_branch(default_branch_name)
|
|
85
96
|
|
|
97
|
+
# Register atexit handler if configured
|
|
98
|
+
if (
|
|
99
|
+
self.config.log_config is not None
|
|
100
|
+
and self.config.log_config.auto_save_on_exit
|
|
101
|
+
and not self._registered_atexit
|
|
102
|
+
):
|
|
103
|
+
atexit.register(self._save_at_exit)
|
|
104
|
+
self._registered_atexit = True
|
|
105
|
+
|
|
86
106
|
# Register built-in operations (lazy import avoids circular)
|
|
87
107
|
from krons.agent.operations import (
|
|
88
108
|
generate,
|
|
@@ -386,6 +406,99 @@ class Session(Element):
|
|
|
386
406
|
async for result in handler(params, ctx):
|
|
387
407
|
yield result
|
|
388
408
|
|
|
409
|
+
def dump_messages(self, clear: bool = False) -> Path | None:
|
|
410
|
+
"""Sync dump all session messages to file.
|
|
411
|
+
|
|
412
|
+
Args:
|
|
413
|
+
clear: Clear messages after dump (default False).
|
|
414
|
+
|
|
415
|
+
Returns:
|
|
416
|
+
Path to written file, or None if no log_config or no messages.
|
|
417
|
+
"""
|
|
418
|
+
from krons.utils import create_path, json_dumpb, json_lines_iter
|
|
419
|
+
from krons.utils.concurrency import run_async
|
|
420
|
+
|
|
421
|
+
if self.config.log_config is None or len(self.messages) == 0:
|
|
422
|
+
return None
|
|
423
|
+
|
|
424
|
+
cfg = self.config.log_config
|
|
425
|
+
self._dump_count += 1
|
|
426
|
+
|
|
427
|
+
filepath = run_async(
|
|
428
|
+
create_path(
|
|
429
|
+
directory=cfg.persist_dir,
|
|
430
|
+
filename=f"session_{str(self.id)[:8]}_{self._dump_count}",
|
|
431
|
+
extension=cfg.extension,
|
|
432
|
+
timestamp=True,
|
|
433
|
+
file_exist_ok=True,
|
|
434
|
+
)
|
|
435
|
+
)
|
|
436
|
+
|
|
437
|
+
items = [msg.to_dict(mode="json") for msg in self.messages]
|
|
438
|
+
|
|
439
|
+
std_path = Path(filepath)
|
|
440
|
+
if cfg.extension == ".jsonl":
|
|
441
|
+
with std_path.open("wb") as f:
|
|
442
|
+
for chunk in json_lines_iter(items, safe_fallback=True):
|
|
443
|
+
f.write(chunk)
|
|
444
|
+
else:
|
|
445
|
+
data = json_dumpb(items, safe_fallback=True)
|
|
446
|
+
std_path.write_bytes(data)
|
|
447
|
+
|
|
448
|
+
if clear:
|
|
449
|
+
self.communications.items.clear()
|
|
450
|
+
|
|
451
|
+
return std_path
|
|
452
|
+
|
|
453
|
+
async def adump_messages(self, clear: bool = False) -> Path | None:
|
|
454
|
+
"""Async dump all session messages to file with lock protection.
|
|
455
|
+
|
|
456
|
+
Args:
|
|
457
|
+
clear: Clear messages after dump (default False).
|
|
458
|
+
|
|
459
|
+
Returns:
|
|
460
|
+
Path to written file, or None if no log_config or no messages.
|
|
461
|
+
"""
|
|
462
|
+
from krons.utils import create_path, json_dumpb, json_lines_iter
|
|
463
|
+
|
|
464
|
+
if self.config.log_config is None or len(self.messages) == 0:
|
|
465
|
+
return None
|
|
466
|
+
|
|
467
|
+
cfg = self.config.log_config
|
|
468
|
+
|
|
469
|
+
async with self.messages:
|
|
470
|
+
self._dump_count += 1
|
|
471
|
+
|
|
472
|
+
filepath = await create_path(
|
|
473
|
+
directory=cfg.persist_dir,
|
|
474
|
+
filename=f"session_{str(self.id)[:8]}_{self._dump_count}",
|
|
475
|
+
extension=cfg.extension,
|
|
476
|
+
timestamp=True,
|
|
477
|
+
file_exist_ok=True,
|
|
478
|
+
)
|
|
479
|
+
|
|
480
|
+
items = [msg.to_dict(mode="json") for msg in self.messages]
|
|
481
|
+
|
|
482
|
+
if cfg.extension == ".jsonl":
|
|
483
|
+
content = b"".join(json_lines_iter(items, safe_fallback=True))
|
|
484
|
+
await filepath.write_bytes(content)
|
|
485
|
+
else:
|
|
486
|
+
data = json_dumpb(items, safe_fallback=True)
|
|
487
|
+
await filepath.write_bytes(data)
|
|
488
|
+
|
|
489
|
+
if clear:
|
|
490
|
+
self.communications.items.clear()
|
|
491
|
+
|
|
492
|
+
return Path(filepath)
|
|
493
|
+
|
|
494
|
+
def _save_at_exit(self) -> None:
|
|
495
|
+
"""atexit callback. Dumps messages synchronously. Errors are suppressed."""
|
|
496
|
+
if len(self.messages) > 0:
|
|
497
|
+
try:
|
|
498
|
+
self.dump_messages(clear=False)
|
|
499
|
+
except Exception:
|
|
500
|
+
pass # Silent failure during interpreter shutdown
|
|
501
|
+
|
|
389
502
|
def _resolve_branch(self, branch: Branch | UUID | str | None) -> Branch:
|
|
390
503
|
"""Resolve to Branch, falling back to default. Raises if neither available."""
|
|
391
504
|
if branch is not None:
|
|
@@ -36,14 +36,15 @@ krons/agent/third_party/anthropic_models.py,sha256=rd2Skh_Xx1S2G1JXi2YFb1j6SHm62
|
|
|
36
36
|
krons/agent/third_party/claude_code.py,sha256=NBYMsdPheb0PqGgTO2_aUAR2XoGDsPUwsB7Nd9PjipA,23758
|
|
37
37
|
krons/agent/third_party/gemini_models.py,sha256=2dag2WbSTKLBd8t0F7t_TXCcl7aqsTFgDI6XLkhsZwM,17416
|
|
38
38
|
krons/agent/third_party/openai_models.py,sha256=L-GkJ-bBW5zwPbgCWEYHMNqRiZfFTAUJE96AVZoDAkQ,8040
|
|
39
|
-
krons/core/__init__.py,sha256=
|
|
40
|
-
krons/core/base/__init__.py,sha256=
|
|
39
|
+
krons/core/__init__.py,sha256=TKi3toVRYybyjUwUJlng--pfWzWihUWQBCNh0UTfTSQ,3611
|
|
40
|
+
krons/core/base/__init__.py,sha256=emyypsuiFK0UzYsDeamD84ygXnO1TLx2A5mBxGTBidE,3755
|
|
41
41
|
krons/core/base/broadcaster.py,sha256=Es__WfL-j2h5NS6aU18fgaFkZsJIL-zDe86Qvlea-io,4013
|
|
42
42
|
krons/core/base/element.py,sha256=LqBbmPttXoDAFy56JzWhSJ4sBw_LW7hMGxZUog85N8g,7642
|
|
43
43
|
krons/core/base/event.py,sha256=iZQ65Xg3a8eaCEl4Hca-pxPNtckhrHkydEqYyppbuo0,11524
|
|
44
44
|
krons/core/base/eventbus.py,sha256=z45ORtGey5A1N9hdyEPOhlX1whaTTdqdk7g3XEDL-_8,3761
|
|
45
45
|
krons/core/base/flow.py,sha256=eqsYm8QO7bD5Y9VYnlpHFWpaUCY0rCeKGrDrFUyBVA4,12364
|
|
46
46
|
krons/core/base/graph.py,sha256=FiTMArxuz6At3oYPEoamES3FtBM-AM6QnA7EQ8G18mo,15972
|
|
47
|
+
krons/core/base/log.py,sha256=SMJlruIG4YHk6Ls45jQ9SrPxFCcnypUmTGaWd90wiwM,923
|
|
47
48
|
krons/core/base/node.py,sha256=0_-R6uNe6-LF3adQRZowSIICdy6ep3cQIQRdF0oosoQ,34699
|
|
48
49
|
krons/core/base/pile.py,sha256=ZKvnX3BjMsrH989TLNZII1dDWv4yPhSRzLKXx2yzyKQ,21108
|
|
49
50
|
krons/core/base/processor.py,sha256=GrdNZa5YD0tXle847ttnxuWe4Ego1MhULzWHmSz5RqQ,18768
|
|
@@ -86,7 +87,7 @@ krons/session/constraints.py,sha256=DvmLKyf5eYUX2nxCDcco1-uDipGjiIJwoUxWVtX2w84,
|
|
|
86
87
|
krons/session/exchange.py,sha256=QChEqK5NFZJpHWxPrXd8LkGBCFg6LNONd1ZEy0MufpI,9165
|
|
87
88
|
krons/session/message.py,sha256=mtZ-PYCCoyWLy7BSB5TL5a0hIt0aQX2zdckOCIl6NZ0,2007
|
|
88
89
|
krons/session/registry.py,sha256=13pPKmDZJaj9q27oUOdtH6-jrHP_HDFF8lCdcNF--EE,999
|
|
89
|
-
krons/session/session.py,sha256=
|
|
90
|
+
krons/session/session.py,sha256=_y3-NEQxgBa51mRd6LiD7zTqJFikKCP4avLqMai6O08,16966
|
|
90
91
|
krons/utils/__init__.py,sha256=V-jTKULdofjJXxcEHygefKV_v6XE4H3poCdxPLjvf_4,1718
|
|
91
92
|
krons/utils/_function_arg_parser.py,sha256=H5JVLBVY8W9ZNkZ4_YVtVVY1rFYvnIRhSrAhKEdj2Qc,3479
|
|
92
93
|
krons/utils/_hash.py,sha256=W2Ma9v8-INPaGkur7GTtbF8KwuXSJNSwk8DCNPRvx8Q,6859
|
|
@@ -145,7 +146,7 @@ krons/work/rules/common/mapping.py,sha256=Loq54MNEtwpnHN0aypTjFOqwoOKLEysddHh-JE
|
|
|
145
146
|
krons/work/rules/common/model.py,sha256=xmM6coEThf_fgIiqJiyDgvdfib_FpVeY6LgWPVcWSwU,3026
|
|
146
147
|
krons/work/rules/common/number.py,sha256=cCukgMSpQu5RdYK5rXAUyop9qXgDRfLCioMvE8kIzHg,3162
|
|
147
148
|
krons/work/rules/common/string.py,sha256=zHp_OLh0FL4PvmSlyDTEzb2I97-DBSEyI2zcMo10voA,5090
|
|
148
|
-
krons-0.2.
|
|
149
|
-
krons-0.2.
|
|
150
|
-
krons-0.2.
|
|
151
|
-
krons-0.2.
|
|
149
|
+
krons-0.2.2.dist-info/METADATA,sha256=X1gYH9GGL-V2GhH3_iy96GzRZOCwO-HKt7NpNrzmgcg,2527
|
|
150
|
+
krons-0.2.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
151
|
+
krons-0.2.2.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
152
|
+
krons-0.2.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|