ph-rlm 0.1.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.
- ph_rlm/__init__.py +18 -0
- ph_rlm/bindings.py +259 -0
- ph_rlm/bundle.yaml +174 -0
- ph_rlm/context_loader.py +595 -0
- ph_rlm/harness/__init__.py +351 -0
- ph_rlm/harness/auto.py +154 -0
- ph_rlm/harness/invariant.py +73 -0
- ph_rlm/harness/planner.py +327 -0
- ph_rlm/harness/service.py +482 -0
- ph_rlm/harness/state.py +308 -0
- ph_rlm/kernel/__init__.py +28 -0
- ph_rlm/kernel/codec.py +126 -0
- ph_rlm/kernel/manager.py +1338 -0
- ph_rlm/kernel/protocol.py +337 -0
- ph_rlm/kernel/venv.py +221 -0
- ph_rlm/keys.py +31 -0
- ph_rlm/messaging.py +532 -0
- ph_rlm/presentation.py +184 -0
- ph_rlm/prompt.py +255 -0
- ph_rlm/py.typed +0 -0
- ph_rlm/skills.py +142 -0
- ph_rlm/snapshot.py +393 -0
- ph_rlm/subagents.py +932 -0
- ph_rlm-0.1.0.dist-info/METADATA +227 -0
- ph_rlm-0.1.0.dist-info/RECORD +28 -0
- ph_rlm-0.1.0.dist-info/WHEEL +4 -0
- ph_rlm-0.1.0.dist-info/entry_points.txt +15 -0
- ph_rlm-0.1.0.dist-info/licenses/LICENSE +21 -0
ph_rlm/__init__.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""pH's RLM bundle: Prime Agent's design implemented on pH's seams.
|
|
2
|
+
|
|
3
|
+
The rule the whole package follows (§6.8): take prime-agent's *semantics* — the
|
|
4
|
+
RLM loop, non-blocking admission, the nuclear-family boundary, the Continual
|
|
5
|
+
Harness, the doctrine prompts — and implement them on pH's seams. Do not take its
|
|
6
|
+
runtime. `ph_rlm.kernel` is pH's own, and `ph_runtime` is its guest half.
|
|
7
|
+
|
|
8
|
+
@module ph_rlm
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
BUNDLE = Path(__file__).parent / "bundle.yaml"
|
|
16
|
+
"""The rows the `rlm` profile layers over `ph-base`."""
|
|
17
|
+
|
|
18
|
+
__all__ = ["BUNDLE"]
|
ph_rlm/bindings.py
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
"""`rlm-bindings` — the `rlm` namespace, as governed tool calls (P3-10, C2/C3).
|
|
2
|
+
|
|
3
|
+
Prime Agent's `await rlm("task", name=..., model=...)` travelled over a Jupyter
|
|
4
|
+
comm channel as a `host.request`, which meant it was invisible to
|
|
5
|
+
`tools/pre-execute`, to `ctx.approval`, to the call limits and to the offload
|
|
6
|
+
policy. Here the same call is a **binding**: one `call` frame out of the kernel,
|
|
7
|
+
through the whole tool pipeline, landing as a durable
|
|
8
|
+
`tool/code-dispatch-start`/`tool/code-dispatch` pair. A deployment can therefore
|
|
9
|
+
deny or `ask` on subagent spawning by policy, and `max_subagent_spawns_per_run`
|
|
10
|
+
counts it, without a line of code here.
|
|
11
|
+
|
|
12
|
+
Two decisions worth stating:
|
|
13
|
+
|
|
14
|
+
**The program-facing name is not the tool name.** A cell writes `rlm.run(...)`;
|
|
15
|
+
the governed tool is `rlm_run`. A namespace cannot claim a bare global name like
|
|
16
|
+
`run` — and it does not need to, because the SDK renders the namespaced form
|
|
17
|
+
while the log records the tool. Only the tools exist as a registry surface, so a
|
|
18
|
+
policy row addresses `rlm_run` and means exactly one thing.
|
|
19
|
+
|
|
20
|
+
**Spawning is `counts_as_spawn`.** The bridge holds it to C4's spawn budget
|
|
21
|
+
rather than the general dispatch budget, so one approved cell cannot fan out
|
|
22
|
+
past `max_subagent_spawns_per_run` on the strength of that one approval.
|
|
23
|
+
|
|
24
|
+
`find_models` is deliberately absent: it would need a model *catalogue* on
|
|
25
|
+
`ctx.llm`, which does not exist yet. Advertising a discovery call that could only
|
|
26
|
+
answer "I don't know" would be worse than not offering one, and the model already
|
|
27
|
+
inherits the parent's model when it names none.
|
|
28
|
+
|
|
29
|
+
@module ph_rlm.bindings
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
from __future__ import annotations
|
|
33
|
+
|
|
34
|
+
from typing import Any
|
|
35
|
+
|
|
36
|
+
from ph.cordis import Context, plugin
|
|
37
|
+
from ph.json import JsonObject, as_str
|
|
38
|
+
from ph.keys import SUBAGENTS, TOOLS
|
|
39
|
+
from ph.llm.types import ContentBlock
|
|
40
|
+
from ph.seams.code_runtime import CodeBindingNamespace
|
|
41
|
+
from ph.seams.subagents import (
|
|
42
|
+
Access,
|
|
43
|
+
DowngradeReason,
|
|
44
|
+
SubagentRequest,
|
|
45
|
+
SubagentSpawnError,
|
|
46
|
+
downgrade_text,
|
|
47
|
+
)
|
|
48
|
+
from ph.tools import ToolModel, ToolOutput, ToolRunContext, define_tool, text_content
|
|
49
|
+
from ph.tools.code_mode import CodeBindingsRequest, ToolCallError, governed_binding
|
|
50
|
+
from ph.wire import WireModel
|
|
51
|
+
|
|
52
|
+
from .keys import RLM_CHILDREN
|
|
53
|
+
from .subagents import PROVIDER_NAME
|
|
54
|
+
|
|
55
|
+
__all__ = ["NAMESPACE", "Config", "apply"]
|
|
56
|
+
|
|
57
|
+
NAMESPACE = "rlm"
|
|
58
|
+
|
|
59
|
+
RUN_TOOL = "rlm_run"
|
|
60
|
+
LIST_TOOL = "rlm_list_subagents"
|
|
61
|
+
DELETE_TOOL = "rlm_delete_subagent"
|
|
62
|
+
|
|
63
|
+
_RUN_DESCRIPTION = (
|
|
64
|
+
"Delegate a task to a child agent. Returns immediately with an admission "
|
|
65
|
+
"handle — never the answer. The child replies by sending you an agent "
|
|
66
|
+
"message, which arrives on a later turn; do not wait or sleep for it."
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class RunArgs(ToolModel):
|
|
71
|
+
"""`rlm.run(...)`. Snake_case because the model types these names."""
|
|
72
|
+
|
|
73
|
+
prompt: str
|
|
74
|
+
name: str | None = None
|
|
75
|
+
model: str | None = None
|
|
76
|
+
thinking: str | None = None
|
|
77
|
+
access: Access = "read"
|
|
78
|
+
"""`read` | `write`. Defaults to `read` (E4): a child that only needs to read
|
|
79
|
+
must not be handed a writable repo because nobody said otherwise. Typed, so
|
|
80
|
+
pydantic validates it and the model sees the two values in the schema."""
|
|
81
|
+
preset: str | None = None
|
|
82
|
+
"""A named kind of child this deployment configured. Fills in what you did not
|
|
83
|
+
name; it cannot give the child more than you have (P4-13b)."""
|
|
84
|
+
skills: tuple[str, ...] | None = None
|
|
85
|
+
"""Skills the child gets, by name from your own catalog. Naming one is also an
|
|
86
|
+
instruction: its full text goes in the child's prompt, so the child starts by
|
|
87
|
+
following that procedure rather than having to fetch it. `None` gives it
|
|
88
|
+
everything you have; you cannot name a skill you do not have (P4-13b)."""
|
|
89
|
+
tools: tuple[str, ...] | None = None
|
|
90
|
+
"""Tools the child may call. `None` gives it everything you have. You cannot
|
|
91
|
+
name a tool you do not have."""
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class DeleteArgs(ToolModel):
|
|
95
|
+
child_id: str
|
|
96
|
+
reason: str = "user"
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class SpawnHandle(WireModel):
|
|
100
|
+
"""What a spawn hands back. Deliberately not the answer."""
|
|
101
|
+
|
|
102
|
+
child_id: str
|
|
103
|
+
name: str
|
|
104
|
+
session_id: str
|
|
105
|
+
model: str
|
|
106
|
+
requested_access: Access
|
|
107
|
+
granted_access: Access
|
|
108
|
+
note: str | None = None
|
|
109
|
+
"""The sentence for `SubagentRun.downgrade_reason`, rendered by the seam.
|
|
110
|
+
|
|
111
|
+
The model needs prose; the *log* keeps the code. One generator, so the two
|
|
112
|
+
cannot disagree and neither goes stale when the workspace tier lands."""
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class Config(WireModel):
|
|
116
|
+
"""Row config for `rlm-bindings`."""
|
|
117
|
+
|
|
118
|
+
provider: str = PROVIDER_NAME
|
|
119
|
+
"""Which `ctx.subagents` provider `rlm.run` delegates to."""
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _render_handle(_args: JsonObject, value: Any) -> list[ContentBlock]: # noqa: ANN401
|
|
123
|
+
lines = [
|
|
124
|
+
f"admitted {value['name']} ({value['childId']}) on {value['model']}",
|
|
125
|
+
f"session: {value['sessionId']}",
|
|
126
|
+
f"workspace access: {value['grantedAccess']} (requested {value['requestedAccess']})",
|
|
127
|
+
]
|
|
128
|
+
if value.get("note"):
|
|
129
|
+
lines.append(as_str(value["note"]))
|
|
130
|
+
lines.append("It will reply by agent message; keep working and check later.")
|
|
131
|
+
return text_content("\n".join(lines))
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
@plugin("rlm-bindings", config=Config, inject=[TOOLS, SUBAGENTS, RLM_CHILDREN])
|
|
135
|
+
async def apply(ctx: Context, config: Config) -> None:
|
|
136
|
+
"""Register the `rlm_*` tools and group them as the `rlm` code namespace."""
|
|
137
|
+
|
|
138
|
+
tools = ctx.require(TOOLS)
|
|
139
|
+
|
|
140
|
+
async def run_child(args: RunArgs, run: ToolRunContext) -> dict[str, Any]:
|
|
141
|
+
if run.agent is None:
|
|
142
|
+
raise ToolCallError(RUN_TOOL, "this tool has to be called by an agent")
|
|
143
|
+
try:
|
|
144
|
+
handle = await ctx.require(SUBAGENTS).start(
|
|
145
|
+
config.provider,
|
|
146
|
+
SubagentRequest(
|
|
147
|
+
prompt=args.prompt,
|
|
148
|
+
parent=run.agent,
|
|
149
|
+
# The boundary the ceiling is computed in, stated rather than
|
|
150
|
+
# derived from the routing target (P6-31, P6-24).
|
|
151
|
+
scope=run.scope,
|
|
152
|
+
name=args.name,
|
|
153
|
+
reasoning_effort=args.thinking,
|
|
154
|
+
model=args.model,
|
|
155
|
+
access=args.access,
|
|
156
|
+
preset=args.preset,
|
|
157
|
+
skills=args.skills,
|
|
158
|
+
tools=args.tools,
|
|
159
|
+
),
|
|
160
|
+
)
|
|
161
|
+
except SubagentSpawnError as error:
|
|
162
|
+
# The model's to handle: it can retry with a different name, a
|
|
163
|
+
# shallower plan, or by doing the work itself.
|
|
164
|
+
raise ToolCallError(RUN_TOOL, str(error)) from error
|
|
165
|
+
reason: DowngradeReason | None = handle.downgrade_reason
|
|
166
|
+
return SpawnHandle(
|
|
167
|
+
child_id=handle.id,
|
|
168
|
+
name=handle.name,
|
|
169
|
+
session_id=handle.session_id,
|
|
170
|
+
model=handle.model,
|
|
171
|
+
requested_access=handle.requested_access,
|
|
172
|
+
granted_access=handle.granted_access,
|
|
173
|
+
note=downgrade_text(reason) if reason is not None else None,
|
|
174
|
+
).to_wire()
|
|
175
|
+
|
|
176
|
+
def list_children(_args: object, run: ToolRunContext) -> dict[str, Any]:
|
|
177
|
+
"""The roster, folded from the parent's own log — never a side table."""
|
|
178
|
+
session = run.session
|
|
179
|
+
rows = list(ctx.require(SUBAGENTS).roster(session).values()) if session is not None else []
|
|
180
|
+
return {"children": rows}
|
|
181
|
+
|
|
182
|
+
async def delete_child(args: DeleteArgs, run: ToolRunContext) -> dict[str, Any]:
|
|
183
|
+
session = run.session
|
|
184
|
+
removed = (
|
|
185
|
+
await ctx.require(RLM_CHILDREN).delete(session, args.child_id, reason=args.reason)
|
|
186
|
+
if session is not None
|
|
187
|
+
else False
|
|
188
|
+
)
|
|
189
|
+
return {"deleted": removed, "childId": args.child_id}
|
|
190
|
+
|
|
191
|
+
tools.register(
|
|
192
|
+
define_tool(
|
|
193
|
+
RUN_TOOL,
|
|
194
|
+
_RUN_DESCRIPTION,
|
|
195
|
+
parameters=RunArgs,
|
|
196
|
+
output=ToolOutput(schema=SpawnHandle, render=_render_handle),
|
|
197
|
+
execute=run_child,
|
|
198
|
+
)
|
|
199
|
+
)
|
|
200
|
+
tools.register(
|
|
201
|
+
define_tool(
|
|
202
|
+
LIST_TOOL,
|
|
203
|
+
"Your children: name, id, status, and whether each was deleted.",
|
|
204
|
+
parameters={"type": "object", "properties": {}},
|
|
205
|
+
output={"type": "object"},
|
|
206
|
+
render=_render_roster,
|
|
207
|
+
execute=list_children,
|
|
208
|
+
is_concurrency_safe=True,
|
|
209
|
+
)
|
|
210
|
+
)
|
|
211
|
+
tools.register(
|
|
212
|
+
define_tool(
|
|
213
|
+
DELETE_TOOL,
|
|
214
|
+
"Revoke a child. Its transcript stays on disk; the roster keeps a tombstone.",
|
|
215
|
+
parameters=DeleteArgs,
|
|
216
|
+
output={"type": "object"},
|
|
217
|
+
render=_render_deleted,
|
|
218
|
+
execute=delete_child,
|
|
219
|
+
)
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
def namespace(request: CodeBindingsRequest) -> CodeBindingNamespace:
|
|
223
|
+
"""`rlm.run` / `.list_subagents` / `.delete_subagent`, bound to the run."""
|
|
224
|
+
view = ctx.require(TOOLS).view(request.scope)
|
|
225
|
+
specs = (
|
|
226
|
+
("run", RUN_TOOL, True),
|
|
227
|
+
("list_subagents", LIST_TOOL, False),
|
|
228
|
+
("delete_subagent", DELETE_TOOL, False),
|
|
229
|
+
)
|
|
230
|
+
bindings = [
|
|
231
|
+
governed_binding(request, public, definition.schema(), counts_as_spawn=spawns)
|
|
232
|
+
for public, tool_name, spawns in specs
|
|
233
|
+
# A tool restricted away for this agent is absent from the SDK block
|
|
234
|
+
# too, so the prompt cannot offer what a cell could not call.
|
|
235
|
+
if (definition := view.visible.get(tool_name)) is not None
|
|
236
|
+
]
|
|
237
|
+
return CodeBindingNamespace(
|
|
238
|
+
name=NAMESPACE,
|
|
239
|
+
description="delegate to child agents; every spawn is governed and recorded",
|
|
240
|
+
bindings=tuple(bindings),
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
tools.register_code_namespace(NAMESPACE, namespace)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def _render_roster(_args: JsonObject, value: Any) -> list[ContentBlock]: # noqa: ANN401
|
|
247
|
+
rows = value.get("children") or []
|
|
248
|
+
if not rows:
|
|
249
|
+
return text_content("no children")
|
|
250
|
+
lines = [
|
|
251
|
+
f"- {row.get('name')} ({row.get('runId')}) "
|
|
252
|
+
f"{'deleted' if row.get('deleted') else row.get('status', 'queued')}"
|
|
253
|
+
for row in rows
|
|
254
|
+
]
|
|
255
|
+
return text_content("\n".join(lines))
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def _render_deleted(_args: JsonObject, value: Any) -> list[ContentBlock]: # noqa: ANN401
|
|
259
|
+
return text_content(f"deleted {value['childId']}" if value["deleted"] else "no such child")
|
ph_rlm/bundle.yaml
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# ph-rlm: Prime Agent's design as plugins, over ph-base.
|
|
2
|
+
#
|
|
3
|
+
# The `rlm` profile is base + tui + this. Every row here is a listener on a seam
|
|
4
|
+
# that already exists; the only core change the bundle needs is the one the
|
|
5
|
+
# `code_runtime` seam already carries — `namespace` and `persistence` (C1).
|
|
6
|
+
|
|
7
|
+
# --- Code Mode --------------------------------------------------------------
|
|
8
|
+
# Opt-in in `ph-base`, on here: the RLM model writes Python, so the transport
|
|
9
|
+
# and its bindings are the whole surface it sees. `tools.mode: code` is what
|
|
10
|
+
# makes a native tool call refuse with the SDK route back (C6).
|
|
11
|
+
- id: tools
|
|
12
|
+
config:
|
|
13
|
+
mode: code
|
|
14
|
+
|
|
15
|
+
- id: tools-code-mode
|
|
16
|
+
name: tools-code-mode
|
|
17
|
+
config:
|
|
18
|
+
maxDispatchesPerRun: 256
|
|
19
|
+
maxSubagentSpawnsPerRun: 32
|
|
20
|
+
maxParallelSubCalls: 10
|
|
21
|
+
|
|
22
|
+
# The model-facing half of Code Mode: the transport is presented as `ipython`,
|
|
23
|
+
# with prime-agent's description verbatim and its result layout. `run_code` stays
|
|
24
|
+
# the reserved internal name — a profile renames the transport, it does not
|
|
25
|
+
# replace it (P3-09).
|
|
26
|
+
- id: rlm-presentation
|
|
27
|
+
name: rlm-presentation
|
|
28
|
+
|
|
29
|
+
# --- the runtime ------------------------------------------------------------
|
|
30
|
+
# pH's own CPython subprocess, fd 3 as the framed channel, one child per agent.
|
|
31
|
+
# `persistence: namespace` is admissible only because the provider promises to
|
|
32
|
+
# emit `kernel/snapshot` events, and the seam checks that promise when it
|
|
33
|
+
# registers (D6) — cross-call state invisible to the log is exactly why dsh
|
|
34
|
+
# withheld a persistent kernel.
|
|
35
|
+
- id: code-runtime-python
|
|
36
|
+
name: code-runtime-python
|
|
37
|
+
config:
|
|
38
|
+
# `managed` builds $PH_CACHE/runtime-venv: ph-runtime-guest, dill, and the
|
|
39
|
+
# Python skills. Nothing else, because everything in it is reachable by
|
|
40
|
+
# model code.
|
|
41
|
+
python: managed
|
|
42
|
+
cpuSeconds: 30
|
|
43
|
+
addressSpaceBytes: 2147483648
|
|
44
|
+
maxLogBytes: 65536
|
|
45
|
+
maxValueBytes: 65536
|
|
46
|
+
maxSnapshotBytes: 16777216
|
|
47
|
+
|
|
48
|
+
# --- delegation ------------------------------------------------------------
|
|
49
|
+
# `rlm()` as a `ctx.subagents` provider: admission is logged and the handle
|
|
50
|
+
# returns before the child answers, so a parent can fan out and keep working
|
|
51
|
+
# (P3-11). The roster the parent reads back is the fold over these events, not a
|
|
52
|
+
# side table, so it survives restart and compaction by construction.
|
|
53
|
+
# E4 note: a child gets `read` unless it asks for `write`, and that default is
|
|
54
|
+
# the seam's (`SubagentRequest.access`) rather than a row knob — until
|
|
55
|
+
# `ctx.workspace` lands (Phase 4, D21) a `write` request is recorded and
|
|
56
|
+
# downgraded with a reason code, because a guarantee nothing enforces must not
|
|
57
|
+
# be granted, and a row knob that could not change that would be a lie.
|
|
58
|
+
# What this host can carry, across every root — the other half of the per-parent
|
|
59
|
+
# figure below, and here rather than in `ph-core` because this is the bundle that
|
|
60
|
+
# produces `subagent` jobs at all. `phern daemon --max-concurrent-children` overrides
|
|
61
|
+
# it. Eight rather than four so two parents fanning out fully is the ordinary
|
|
62
|
+
# case and not a queue; a starting point, not a measurement.
|
|
63
|
+
- id: jobs
|
|
64
|
+
config:
|
|
65
|
+
concurrency:
|
|
66
|
+
subagent: 8
|
|
67
|
+
|
|
68
|
+
- id: rlm-subagent-provider
|
|
69
|
+
name: rlm-subagent-provider
|
|
70
|
+
config:
|
|
71
|
+
maxDepth: 2
|
|
72
|
+
# How many of one parent's children run at once; the rest queue in
|
|
73
|
+
# admission order rather than being refused. A deployment number, not a
|
|
74
|
+
# row default: the row says `null` (no cap) so a profile that never chose is
|
|
75
|
+
# not silently serialised. Four is a starting point, not a measurement.
|
|
76
|
+
maxConcurrent: 4
|
|
77
|
+
|
|
78
|
+
# The model-facing half: `rlm.run(...)` inside a cell is a governed dispatch,
|
|
79
|
+
# not a comm-channel RPC — so a policy row can deny or ask on spawning, and C4's
|
|
80
|
+
# spawn budget counts it (P3-10).
|
|
81
|
+
- id: rlm-bindings
|
|
82
|
+
name: rlm-bindings
|
|
83
|
+
|
|
84
|
+
# Agent-to-agent messages (P3-12). The nuclear-family boundary is a *guard* —
|
|
85
|
+
# deny-only, last, and not re-permittable by any later listener (C7) — while the
|
|
86
|
+
# rate limit is backpressure the tool body raises, because ending a whole cell
|
|
87
|
+
# over four messages in a second is not what a rate limit means.
|
|
88
|
+
- id: rlm-messaging
|
|
89
|
+
name: rlm-messaging
|
|
90
|
+
config:
|
|
91
|
+
maxMessageChars: 16384
|
|
92
|
+
maxPending: 20
|
|
93
|
+
rateCapacity: 3
|
|
94
|
+
rateRefillSeconds: 1.0
|
|
95
|
+
|
|
96
|
+
# The doctrine (P3-14). Sections after `tools:sdk`, because they refer to the
|
|
97
|
+
# surface it has just described — and the volatile facts (depth, cwd, family,
|
|
98
|
+
# workspace tier) are a `context()` snapshot rather than a cached section, or
|
|
99
|
+
# every turn that changed one would re-bill the whole prefix (A12).
|
|
100
|
+
- id: rlm-prompt
|
|
101
|
+
name: rlm-prompt
|
|
102
|
+
|
|
103
|
+
# The Continual Harness (P3-16, D14): state is a fold over `harness/refined`
|
|
104
|
+
# rather than a file, so a fork inherits the harness as of its boundary and a
|
|
105
|
+
# rollback is derivable from the event that made the change. `/refine` is a
|
|
106
|
+
# command, not a tool — it is something the human asks for.
|
|
107
|
+
- id: rlm-harness
|
|
108
|
+
name: rlm-harness
|
|
109
|
+
|
|
110
|
+
# I6's live example (P6-01). `harness_state.json` is written for humans and
|
|
111
|
+
# nothing reads it back, which is exactly the arrangement that lets a projection
|
|
112
|
+
# drift unnoticed — so the invariant is checked rather than assumed.
|
|
113
|
+
- id: rlm-harness-invariant
|
|
114
|
+
name: rlm-harness-invariant
|
|
115
|
+
|
|
116
|
+
# Capability-layer skills (P3-18): `SKILL.md` for the catalog, an installable
|
|
117
|
+
# package for the kernel. The knowledge layer (`/refine`) may point at these and
|
|
118
|
+
# may not create them — I7, and the reason the two share a word, not a mechanism.
|
|
119
|
+
# No `paths` means no skills, so the row costs nothing until a deployment
|
|
120
|
+
# configures one.
|
|
121
|
+
- id: rlm-skills-python
|
|
122
|
+
name: rlm-skills-python
|
|
123
|
+
|
|
124
|
+
# `subagent-task` stands down here. Both delegation shapes are real — one call
|
|
125
|
+
# and one answer, or a handle collected later — but this bundle *is* the second
|
|
126
|
+
# one: `rlm_run` admits a child that replies by agent message, with a roster and
|
|
127
|
+
# an inbox built around that. Offering a blocking twin beside it would put two
|
|
128
|
+
# ways to do one thing in the same prompt, and the model would have to guess
|
|
129
|
+
# which of them the rest of the tools were designed for.
|
|
130
|
+
- id: subagent-task
|
|
131
|
+
disabled: true
|
|
132
|
+
|
|
133
|
+
# "Prompt-as-a-variable" (P3-17, Q4): a corpus the model queries through
|
|
134
|
+
# `await tools.context_search(...)` — bindings, so each query is its own governed
|
|
135
|
+
# dispatch and one oversized result can be offloaded without its siblings.
|
|
136
|
+
# Disabled here and opt-in in `rlm-stable`: C3 gave the model governed
|
|
137
|
+
# `read`/`grep`/`glob`, which demoted this from a headline feature to a
|
|
138
|
+
# specialist one for non-file corpora. A row with no `sources` stands down anyway.
|
|
139
|
+
- id: rlm-context-loader
|
|
140
|
+
name: rlm-context-loader
|
|
141
|
+
disabled: true
|
|
142
|
+
|
|
143
|
+
# The row that makes `persistence: namespace` true rather than declared (D17).
|
|
144
|
+
# Mounted with the runtime, not optionally: the seam takes the provider's promise
|
|
145
|
+
# at registration, and these events are the promise being kept.
|
|
146
|
+
- id: rlm-kernel-snapshot
|
|
147
|
+
name: rlm-kernel-snapshot
|
|
148
|
+
config:
|
|
149
|
+
inlineBlobMax: 65536
|
|
150
|
+
|
|
151
|
+
# The containment ladder for an RLM session (P4-11, §4.8). The *root* agent
|
|
152
|
+
# stays `advisory`: a person running `rlm` is working in the directory they
|
|
153
|
+
# opened, and moving their agent to a checkout elsewhere is a surprise a
|
|
154
|
+
# deployment should have to ask for. Children go to `worktree`, because eight of
|
|
155
|
+
# them writing one tree concurrently is the fan-out hazard the tier exists for —
|
|
156
|
+
# and the parent then reviews a diff instead of trusting sibling writes (E2).
|
|
157
|
+
- id: containment
|
|
158
|
+
config:
|
|
159
|
+
tier: advisory
|
|
160
|
+
child_tier: worktree
|
|
161
|
+
|
|
162
|
+
# The rungs themselves. Layered here rather than in `ph-base` because they cost
|
|
163
|
+
# a checkout per agent, and inert until `containment` above asks for them.
|
|
164
|
+
- id: workspace-git-worktree
|
|
165
|
+
name: workspace-git-worktree
|
|
166
|
+
|
|
167
|
+
- id: workspace-checkpoint
|
|
168
|
+
name: workspace-checkpoint
|
|
169
|
+
|
|
170
|
+
- id: workspace-commands
|
|
171
|
+
name: workspace-commands
|
|
172
|
+
|
|
173
|
+
- id: workspace-revert
|
|
174
|
+
name: workspace-revert
|