symphony-harness 0.0.1__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.
@@ -0,0 +1,88 @@
1
+ from core_harness.addons import (
2
+ Addon,
3
+ ChildConfig,
4
+ ChildIdentity,
5
+ CompactionAddon,
6
+ Compactor,
7
+ KeepDropPlan,
8
+ KeepSystemRecentCompactor,
9
+ PersistenceAddon,
10
+ SubagentAddon,
11
+ compaction_from_config,
12
+ plan_keep_drop,
13
+ )
14
+ from core_harness.addons.persistence import (
15
+ Checkpoint,
16
+ NullPersistence,
17
+ Persistence,
18
+ )
19
+ from core_harness.context import (
20
+ ContextReport,
21
+ bound_tool_result,
22
+ build_context_report,
23
+ )
24
+ from core_harness.config import (
25
+ HarnessConfig,
26
+ load_harness_config,
27
+ resolve_harness_config,
28
+ )
29
+ from core_harness.events import EventLog, EventSink, HumanInteraction, InteractionPort
30
+ from core_harness.ops import Answer, Interrupt, Op, UserInput
31
+ from core_harness.errors import HarnessCancelled, HarnessLimitExceeded
32
+ from core_harness.harness import CoreHarness
33
+ from core_harness.models import (
34
+ ControlPlaneEvent,
35
+ ControlPlaneEventType,
36
+ EVENT_SCHEMA_VERSION,
37
+ HarnessResult,
38
+ PendingToolCall,
39
+ RunLimits,
40
+ ToolCall,
41
+ ToolResult,
42
+ UsageTotals,
43
+ )
44
+ from core_harness.tools import Tool
45
+
46
+ __all__ = [
47
+ "Answer",
48
+ "Addon",
49
+ "Checkpoint",
50
+ "ChildConfig",
51
+ "ChildIdentity",
52
+ "CompactionAddon",
53
+ "Compactor",
54
+ "ContextReport",
55
+ "ControlPlaneEvent",
56
+ "ControlPlaneEventType",
57
+ "CoreHarness",
58
+ "EVENT_SCHEMA_VERSION",
59
+ "EventLog",
60
+ "EventSink",
61
+ "HumanInteraction",
62
+ "InteractionPort",
63
+ "Interrupt",
64
+ "Op",
65
+ "UserInput",
66
+ "HarnessCancelled",
67
+ "HarnessLimitExceeded",
68
+ "HarnessResult",
69
+ "HarnessConfig",
70
+ "KeepDropPlan",
71
+ "KeepSystemRecentCompactor",
72
+ "NullPersistence",
73
+ "PendingToolCall",
74
+ "Persistence",
75
+ "PersistenceAddon",
76
+ "RunLimits",
77
+ "SubagentAddon",
78
+ "Tool",
79
+ "ToolCall",
80
+ "ToolResult",
81
+ "UsageTotals",
82
+ "bound_tool_result",
83
+ "build_context_report",
84
+ "compaction_from_config",
85
+ "load_harness_config",
86
+ "resolve_harness_config",
87
+ "plan_keep_drop",
88
+ ]
@@ -0,0 +1,45 @@
1
+ """Opt-in harness add-ons.
2
+
3
+ ``CoreHarness`` is an extension surface. Pass ``addons=[...]`` at construction
4
+ or call ``register_addon``. There is no directory discovery and no skill
5
+ loader; skills can hang off this same attach path later.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from core_harness.addons.addon import Addon
11
+ from core_harness.addons.compaction import (
12
+ CompactionAddon,
13
+ Compactor,
14
+ KeepDropPlan,
15
+ KeepSystemRecentCompactor,
16
+ compaction_from_config,
17
+ plan_keep_drop,
18
+ )
19
+ from core_harness.addons.persistence import (
20
+ Checkpoint,
21
+ CheckpointStatus,
22
+ NullPersistence,
23
+ Persistence,
24
+ PersistenceAddon,
25
+ )
26
+ from core_harness.addons.subagent import ChildConfig, ChildIdentity, SubagentAddon
27
+
28
+
29
+ __all__ = [
30
+ "Addon",
31
+ "Checkpoint",
32
+ "CheckpointStatus",
33
+ "ChildConfig",
34
+ "ChildIdentity",
35
+ "CompactionAddon",
36
+ "Compactor",
37
+ "KeepDropPlan",
38
+ "KeepSystemRecentCompactor",
39
+ "NullPersistence",
40
+ "Persistence",
41
+ "PersistenceAddon",
42
+ "SubagentAddon",
43
+ "compaction_from_config",
44
+ "plan_keep_drop",
45
+ ]
@@ -0,0 +1,67 @@
1
+ """Attachable harness extensions.
2
+
3
+ Subclass :class:`Addon` and override hooks to opt in.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from typing import Any, Optional
9
+
10
+
11
+ class Addon:
12
+ """Harness extension. Hooks are no-ops until overridden.
13
+
14
+ Public seams (override to opt in): ``before_run``, ``before_turn``,
15
+ ``before_tool``, ``after_turn``, ``after_run``, ``on_tool``,
16
+ ``on_compact``. ``before_tool`` may return a deny reason; the others
17
+ observe. Inherit on spawn by implementing :meth:`fork_for_child`.
18
+ """
19
+
20
+ name: str = ""
21
+
22
+ def attach(self, harness: Any) -> None:
23
+ """Mount this add-on onto a harness. Called once from ``register_addon``."""
24
+ del harness
25
+ return None
26
+
27
+ def fork_for_child(self, parent_harness: Any) -> Optional[Addon]:
28
+ """Child inherit policy: return an instance to inherit, or ``None`` to skip.
29
+
30
+ The default skips, so children do not receive this instance.
31
+ ``ChildConfig.addons`` / ``addon_factory`` replace this path when set.
32
+ """
33
+ del parent_harness
34
+ return None
35
+
36
+ async def before_run(self, **payload: Any) -> None:
37
+ del payload
38
+ return None
39
+
40
+ async def before_turn(self, **payload: Any) -> None:
41
+ del payload
42
+ return None
43
+
44
+ async def before_tool(self, **payload: Any) -> Optional[str]:
45
+ """Return a deny reason to skip the tool, or ``None`` to allow it."""
46
+ del payload
47
+ return None
48
+
49
+ async def after_turn(self, **payload: Any) -> None:
50
+ del payload
51
+ return None
52
+
53
+ async def after_run(self, **payload: Any) -> None:
54
+ """Called after a successful run, before ``CoreHarness.run`` returns."""
55
+ del payload
56
+ return None
57
+
58
+ async def on_tool(self, **payload: Any) -> None:
59
+ del payload
60
+ return None
61
+
62
+ async def on_compact(self, **payload: Any) -> None:
63
+ del payload
64
+ return None
65
+
66
+
67
+ __all__ = ["Addon"]
@@ -0,0 +1,76 @@
1
+ """Compaction add-on: mount a ``Compactor`` onto the harness."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Optional
6
+
7
+ from core_harness.addons.addon import Addon
8
+ from core_harness.addons.compaction.policy import (
9
+ COMPACTION_CONTINUATION,
10
+ Compactor,
11
+ KeepDropPlan,
12
+ KeepSystemRecentCompactor,
13
+ compaction_header,
14
+ compaction_target,
15
+ dropped_turn_facts,
16
+ plan_keep_drop,
17
+ summarize_dropped_turns,
18
+ )
19
+ from core_harness.config import HarnessConfig
20
+
21
+
22
+ class CompactionAddon(Addon):
23
+ """Mount a ``Compactor`` onto ``CoreHarness.state``."""
24
+
25
+ name = "compaction"
26
+
27
+ def __init__(
28
+ self,
29
+ compactor: Compactor | None = None,
30
+ *,
31
+ keep_recent_tools: int = 32,
32
+ target_tokens: Optional[int] = None,
33
+ ) -> None:
34
+ self.compactor = compactor or KeepSystemRecentCompactor(
35
+ keep_recent_tools=keep_recent_tools,
36
+ target_tokens=target_tokens,
37
+ )
38
+ if isinstance(self.compactor, KeepSystemRecentCompactor):
39
+ keep_recent_tools = self.compactor.keep_recent_tools
40
+ target_tokens = self.compactor.target_tokens
41
+ self.keep_recent_tools = keep_recent_tools
42
+ self.target_tokens = target_tokens
43
+
44
+ def attach(self, harness: Any) -> None:
45
+ harness.state.compactor = self.compactor
46
+
47
+ def fork_for_child(self, parent_harness: Any) -> CompactionAddon:
48
+ """New add-on with a fresh compactor using the same settings."""
49
+ del parent_harness
50
+ return CompactionAddon(
51
+ keep_recent_tools=self.keep_recent_tools,
52
+ target_tokens=self.target_tokens,
53
+ )
54
+
55
+
56
+ def compaction_from_config(config: HarnessConfig) -> CompactionAddon:
57
+ """Build the keep-system-recent add-on from harness settings."""
58
+ return CompactionAddon(
59
+ keep_recent_tools=config.compaction_keep_recent_tools,
60
+ target_tokens=config.context_target_tokens,
61
+ )
62
+
63
+
64
+ __all__ = [
65
+ "COMPACTION_CONTINUATION",
66
+ "CompactionAddon",
67
+ "Compactor",
68
+ "KeepDropPlan",
69
+ "KeepSystemRecentCompactor",
70
+ "compaction_from_config",
71
+ "compaction_header",
72
+ "compaction_target",
73
+ "dropped_turn_facts",
74
+ "plan_keep_drop",
75
+ "summarize_dropped_turns",
76
+ ]
@@ -0,0 +1,335 @@
1
+ """Keep-system-recent compaction policy.
2
+
3
+ ``plan_keep_drop`` is the shared keep/drop rule: system prompt, pinned first
4
+ user task, atomic assistant/tool groups, recent window, token target.
5
+ ``KeepSystemRecentCompactor`` applies it and writes a template summary with no
6
+ provider call. Products that summarise with a model call the same planner and
7
+ supply their own summary message.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from collections import Counter
13
+ from dataclasses import dataclass, field
14
+ from typing import Dict, List, Optional, Protocol, Sequence
15
+
16
+ from core_ai.content import text_from_content
17
+ from core_ai.types import Content, Message
18
+
19
+ from core_harness.context.compact import (
20
+ COMPACTED_CONTEXT_MARK,
21
+ estimate_prompt_tokens,
22
+ normalize_tool_protocol,
23
+ _tool_names,
24
+ _tool_refs,
25
+ )
26
+
27
+
28
+ def _atomic_blocks(messages: List[Message]) -> List[List[Message]]:
29
+ """Group an assistant tool declaration with all of its tool results."""
30
+ blocks: List[List[Message]] = []
31
+ index = 0
32
+ while index < len(messages):
33
+ message = messages[index]
34
+ if message.role == "assistant" and message.tool_calls:
35
+ block = [message]
36
+ index += 1
37
+ while index < len(messages) and messages[index].role == "tool":
38
+ block.append(messages[index])
39
+ index += 1
40
+ blocks.append(block)
41
+ else:
42
+ blocks.append([message])
43
+ index += 1
44
+ return blocks
45
+
46
+
47
+ def _first_user_message(messages: Sequence[Message]) -> Optional[Message]:
48
+ for message in messages:
49
+ if message.role != "user":
50
+ continue
51
+ if text_from_content(message.content).startswith(COMPACTED_CONTEXT_MARK):
52
+ continue
53
+ return message
54
+ return None
55
+
56
+
57
+ def _preview_ask(content: Content) -> str:
58
+ text = " ".join(text_from_content(content).split())
59
+ if len(text) > 120:
60
+ return text[:117] + "…"
61
+ return text
62
+
63
+
64
+ COMPACTION_CONTINUATION = (
65
+ "Continue from the messages below. Do not re-fetch already observed "
66
+ "paths unless they changed. Do not ask the user to repeat dropped work."
67
+ )
68
+
69
+
70
+ def compaction_header(dropped_messages: int, *, tokens: int) -> List[str]:
71
+ """Opening lines shared by every compacted-context message."""
72
+ return [
73
+ COMPACTED_CONTEXT_MARK,
74
+ f"Dropped {dropped_messages} earlier message(s) (~{tokens:,} tokens).",
75
+ ]
76
+
77
+
78
+ def dropped_turn_facts(turns: Sequence[Sequence[Message]]) -> List[str]:
79
+ """Deterministic facts about dropped turns: asks, tools used, paths seen."""
80
+ asks: List[str] = []
81
+ tools: Counter[str] = Counter()
82
+ observed: List[str] = []
83
+ names: Dict[str, str] = {}
84
+ refs: Dict[str, str] = {}
85
+ for turn in turns:
86
+ names.update(_tool_names(turn))
87
+ refs.update(_tool_refs(turn))
88
+ for message in turn:
89
+ if message.role == "user":
90
+ preview = _preview_ask(message.content)
91
+ if preview and not preview.startswith(COMPACTED_CONTEXT_MARK):
92
+ asks.append(preview)
93
+ elif message.role == "tool":
94
+ name = names.get(str(message.tool_call_id or ""), "tool")
95
+ tools[name] += 1
96
+ ref = refs.get(str(message.tool_call_id or ""), "")
97
+ if ref and ref not in observed:
98
+ observed.append(ref)
99
+
100
+ lines: List[str] = []
101
+ if asks:
102
+ shown = asks[:6]
103
+ preview = "; ".join(f'"{ask}"' for ask in shown)
104
+ if len(asks) > 6:
105
+ preview += f"; … ({len(asks) - 6} more)"
106
+ lines.append(f"User asks: {preview}")
107
+ if tools:
108
+ used = ", ".join(
109
+ f"{name}×{count}" if count > 1 else name
110
+ for name, count in tools.items()
111
+ )
112
+ lines.append(f"Tools used: {used}")
113
+ if observed:
114
+ shown_obs = observed[:10]
115
+ extra = f"; … ({len(observed) - 10} more)" if len(observed) > 10 else ""
116
+ lines.append("Already observed: " + "; ".join(shown_obs) + extra)
117
+ return lines
118
+
119
+
120
+ def summarize_dropped_turns(turns: Sequence[Sequence[Message]], *, tokens: int) -> Message:
121
+ """Template summary of dropped turns; no model call."""
122
+ dropped_messages = sum(len(turn) for turn in turns)
123
+ lines = compaction_header(dropped_messages, tokens=tokens)
124
+ lines.extend(dropped_turn_facts(turns))
125
+ lines.append(COMPACTION_CONTINUATION)
126
+ return Message(role="user", content="\n".join(lines))
127
+
128
+
129
+ class Compactor(Protocol):
130
+ async def compact(
131
+ self,
132
+ messages: List[Message],
133
+ *,
134
+ turn: int,
135
+ context_limit: Optional[int],
136
+ tokens_used: int,
137
+ context_left: Optional[int],
138
+ ) -> List[Message]:
139
+ """Return a reduced message list that fits better in the context window."""
140
+
141
+
142
+ def _tool_result_count(blocks: Sequence[Sequence[Message]]) -> int:
143
+ return sum(message.role == "tool" for block in blocks for message in block)
144
+
145
+
146
+ def _split_recent_tool_blocks(
147
+ remainder: List[List[Message]],
148
+ *,
149
+ keep_recent_tools: int,
150
+ ) -> tuple[List[List[Message]], List[List[Message]]]:
151
+ """Keep the suffix containing the latest tool results and their call groups.
152
+
153
+ When a conversation has no tools, keep the latest ``keep_recent_tools``
154
+ message blocks so text-only conversations can still be compacted.
155
+ """
156
+ total_tools = _tool_result_count(remainder)
157
+ if total_tools == 0:
158
+ split = max(len(remainder) - keep_recent_tools, 0)
159
+ return remainder[:split], remainder[split:]
160
+
161
+ wanted = min(keep_recent_tools, total_tools)
162
+ selected: List[List[Message]] = []
163
+ selected_tools = 0
164
+ dropped_end = 0
165
+ for index in range(len(remainder) - 1, -1, -1):
166
+ block = remainder[index]
167
+ selected.insert(0, block)
168
+ selected_tools += _tool_result_count([block])
169
+ dropped_end = index
170
+ if selected_tools >= wanted:
171
+ previous = index - 1
172
+ if previous >= 0 and any(
173
+ message.role == "user" for message in remainder[previous]
174
+ ):
175
+ selected.insert(0, remainder[previous])
176
+ dropped_end = previous
177
+ break
178
+ else:
179
+ dropped_end = 0
180
+ selected = list(remainder)
181
+ return remainder[:dropped_end], selected
182
+
183
+
184
+ def _flatten(blocks: Sequence[Sequence[Message]]) -> List[Message]:
185
+ return [message for group in blocks for message in group]
186
+
187
+
188
+ def compaction_target(
189
+ target_tokens: Optional[int],
190
+ context_limit: Optional[int],
191
+ ) -> Optional[int]:
192
+ """Token budget a compact should land under; a third of the window by default."""
193
+ if target_tokens is not None:
194
+ return target_tokens
195
+ if context_limit is not None:
196
+ return max(context_limit // 3, 1)
197
+ return None
198
+
199
+
200
+ @dataclass
201
+ class KeepDropPlan:
202
+ """Outcome of ``plan_keep_drop``.
203
+
204
+ ``leading`` is the system prompt plus the pinned original task; ``dropped``
205
+ and ``kept`` are atomic assistant/tool groups in conversation order.
206
+ """
207
+
208
+ leading: List[Message] = field(default_factory=list)
209
+ dropped: List[List[Message]] = field(default_factory=list)
210
+ kept: List[List[Message]] = field(default_factory=list)
211
+
212
+ @property
213
+ def dropped_messages(self) -> List[Message]:
214
+ return _flatten(self.dropped)
215
+
216
+ @property
217
+ def dropped_tokens(self) -> int:
218
+ return estimate_prompt_tokens(self.dropped_messages)
219
+
220
+ def assemble(self, summary: Optional[Message]) -> List[Message]:
221
+ """``leading`` + ``summary`` (when there is dropped history) + ``kept``."""
222
+ middle = [summary] if summary is not None and self.dropped else []
223
+ return self.leading + middle + _flatten(self.kept)
224
+
225
+ def template_summary(self) -> Optional[Message]:
226
+ if not self.dropped:
227
+ return None
228
+ return summarize_dropped_turns(self.dropped, tokens=self.dropped_tokens)
229
+
230
+
231
+ def plan_keep_drop(
232
+ messages: List[Message],
233
+ *,
234
+ keep_recent_tools: int,
235
+ target_tokens: Optional[int] = None,
236
+ context_limit: Optional[int] = None,
237
+ ) -> KeepDropPlan:
238
+ """Decide what to keep and what to drop; no summary is written here.
239
+
240
+ Keeps the leading system prompt, the first real user message, and the suffix
241
+ containing the latest ``keep_recent_tools`` tool results. Assistant/tool
242
+ groups stay together so the provider protocol stays valid. The token target
243
+ is best-effort: protected recent tool results are never stubbed or dropped.
244
+ """
245
+ if keep_recent_tools < 1:
246
+ raise ValueError("keep_recent_tools must be >= 1")
247
+ valid = normalize_tool_protocol(messages)
248
+ system = valid[0] if valid and valid[0].role == "system" else None
249
+ rest = valid[1:] if system is not None else valid
250
+ plan = KeepDropPlan(leading=[system] if system is not None else [])
251
+ if not rest:
252
+ return plan
253
+
254
+ first_user = _first_user_message(rest)
255
+ remainder: List[List[Message]] = []
256
+ for block in _atomic_blocks(rest):
257
+ if first_user is not None and first_user in block:
258
+ leftover = [message for message in block if message is not first_user]
259
+ if leftover:
260
+ remainder.append(leftover)
261
+ else:
262
+ remainder.append(block)
263
+ if first_user is not None:
264
+ plan.leading.append(first_user)
265
+
266
+ plan.dropped, plan.kept = _split_recent_tool_blocks(
267
+ remainder, keep_recent_tools=keep_recent_tools
268
+ )
269
+ target = compaction_target(target_tokens, context_limit)
270
+ protected_tools = min(keep_recent_tools, _tool_result_count(remainder))
271
+ while (
272
+ target is not None
273
+ and estimate_prompt_tokens(plan.assemble(plan.template_summary())) > target
274
+ and len(plan.kept) > 1
275
+ ):
276
+ if protected_tools:
277
+ break
278
+ plan.dropped.append(plan.kept.pop(0))
279
+ return plan
280
+
281
+
282
+ class KeepSystemRecentCompactor:
283
+ """Keep the system prompt, the original task, and the most recent messages.
284
+
285
+ Assistant/tool groups stay together so the provider protocol stays valid.
286
+ Earlier tool groups are summarised while the latest tool results remain in
287
+ full. Dropped messages become one deterministic template summary; this
288
+ compactor never calls a provider.
289
+ """
290
+
291
+ def __init__(
292
+ self,
293
+ keep_recent_tools: int = 32,
294
+ target_tokens: Optional[int] = None,
295
+ ) -> None:
296
+ if keep_recent_tools < 1:
297
+ raise ValueError("keep_recent_tools must be >= 1")
298
+ if target_tokens is not None and target_tokens < 1:
299
+ raise ValueError("target_tokens must be positive or None")
300
+ self.keep_recent_tools = keep_recent_tools
301
+ self.target_tokens = target_tokens
302
+
303
+ def plan(self, messages: List[Message], *, context_limit: Optional[int]) -> KeepDropPlan:
304
+ return plan_keep_drop(
305
+ messages,
306
+ keep_recent_tools=self.keep_recent_tools,
307
+ target_tokens=self.target_tokens,
308
+ context_limit=context_limit,
309
+ )
310
+
311
+ async def compact(
312
+ self,
313
+ messages: List[Message],
314
+ *,
315
+ turn: int,
316
+ context_limit: Optional[int],
317
+ tokens_used: int,
318
+ context_left: Optional[int],
319
+ ) -> List[Message]:
320
+ del turn, tokens_used, context_left
321
+ plan = self.plan(messages, context_limit=context_limit)
322
+ return plan.assemble(plan.template_summary())
323
+
324
+
325
+ __all__ = [
326
+ "COMPACTION_CONTINUATION",
327
+ "Compactor",
328
+ "KeepDropPlan",
329
+ "KeepSystemRecentCompactor",
330
+ "compaction_header",
331
+ "compaction_target",
332
+ "dropped_turn_facts",
333
+ "plan_keep_drop",
334
+ "summarize_dropped_turns",
335
+ ]