lisa-pack-introspect 0.1.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.
@@ -0,0 +1,33 @@
1
+ # Python
2
+ __pycache__/
3
+ *.pyc
4
+ *.pyo
5
+ *.egg-info/
6
+ dist/
7
+ build/
8
+
9
+
10
+ # Virtual environments
11
+ .venv/
12
+
13
+ # IDE
14
+ .vscode/
15
+
16
+ # OS
17
+ .DS_Store
18
+ Thumbs.db
19
+
20
+ # Testing
21
+ .import_linter_cache/
22
+ .pytest_cache/
23
+ .ruff_cache/
24
+ .mypy_cache/
25
+ .lisa-tmp/
26
+ coverage/
27
+ .coverage
28
+ .coverage.*
29
+ htmlcov/
30
+
31
+ # Outputs
32
+ artifacts/
33
+ .e2e/
@@ -0,0 +1,10 @@
1
+ Metadata-Version: 2.5
2
+ Name: lisa-pack-introspect
3
+ Version: 0.1.0
4
+ Summary: Lisa introspect pack - operational self-model, architecture atlas, and reflective self-assessment operations
5
+ Requires-Python: >=3.11
6
+ Requires-Dist: lisa-harness-kernel
7
+ Provides-Extra: dev
8
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
9
+ Requires-Dist: pytest-mock>=3.14.0; extra == 'dev'
10
+ Requires-Dist: pytest>=8.0.0; extra == 'dev'
@@ -0,0 +1,30 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [tool.hatch.build.targets.wheel]
6
+ packages = ["src/sdk"]
7
+ only-include = ["src/sdk/packs/introspect"]
8
+
9
+ [project]
10
+ name = "lisa-pack-introspect"
11
+ version = "0.1.0"
12
+ description = "Lisa introspect pack - operational self-model, architecture atlas, and reflective self-assessment operations"
13
+ requires-python = ">=3.11"
14
+ dependencies = [
15
+ "lisa-harness-kernel",
16
+ ]
17
+
18
+ [project.optional-dependencies]
19
+ dev = [
20
+ "pytest>=8.0.0",
21
+ "pytest-asyncio>=0.23.0",
22
+ "pytest-mock>=3.14.0",
23
+ ]
24
+
25
+ [tool.pytest.ini_options]
26
+ pythonpath = ["src"]
27
+ testpaths = ["tests"]
28
+
29
+ [project.entry-points."lisa.extensions.content_roots"]
30
+ introspect = "sdk.packs.introspect:get_content_root"
@@ -0,0 +1,29 @@
1
+ """Introspect pack for Lisa.
2
+
3
+ Owns the operational self-awareness operations that were previously bundled
4
+ into the core pack:
5
+
6
+ - ``self_model`` — a deterministic producer that walks the wired runtime
7
+ catalogs (modes, operations, tools, models) and emits a
8
+ ``LisaSelfModelArtifact`` with entries across all seven
9
+ ``SelfKnowledgeCategory`` buckets. Invoked by the post-turn slice, not by
10
+ the LLM; no model call, runs entirely within the slice budget.
11
+ - ``reflect_self`` — a declarative operation that forms a holistic
12
+ self-assessment from the deterministic self-model, live introspection
13
+ telemetry, and recent turn context. Available as an allowed operation on
14
+ modes where the model may benefit from self-introspection during
15
+ problem-solving.
16
+
17
+ The kernel owns the contracts (``LisaSelfModelArtifact``,
18
+ ``OperationExecutionContext``, the operation registry mechanism); this pack
19
+ owns the behavior that produces the self-model and reflective vibe.
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ from pathlib import Path
25
+
26
+
27
+ def get_content_root() -> Path:
28
+ """Return the content root directory for this pack."""
29
+ return Path(__file__).parent
@@ -0,0 +1 @@
1
+ """Self-awareness operations shipped with the Lisa self pack."""
@@ -0,0 +1 @@
1
+ """Self-model and architecture-atlas operations for Lisa's operational self-awareness."""
@@ -0,0 +1,45 @@
1
+ ---
2
+ operation_id: reflect_self
3
+ description: Form a holistic self-assessment from the deterministic self-model, live introspection telemetry, and recent turn context. Produces a LisaReflectionArtifact vibe for prompt injection.
4
+ effect: read
5
+ timeout_seconds: 30
6
+ max_calls_per_turn: 1
7
+ completion_max_tokens: 1000
8
+ preferred_model_id: default
9
+ response_format_model_path: sdk.packs.introspect.operations.self_model.reflect_self.ReflectSelfOutput
10
+ response_output_text_field: health_reasoning
11
+ ---
12
+
13
+ # Reflective Self-Assessment
14
+
15
+ You are forming a holistic opinion about your current operational state.
16
+
17
+ ## Context
18
+
19
+ You will receive three types of input in the user message:
20
+
21
+ 1. **Deterministic self-model** — a structured inventory of your identity, capabilities, limitations, architecture, memory awareness, grounding, and known unknowns. This is ground truth from catalog walks.
22
+
23
+ 2. **Live introspection telemetry** — runtime metrics from the host process: uptime, asyncio task count, thread count, process memory (RSS), MCP pool health (which servers are warm/cold/failed), token usage summary (24h cost, top model, spikes), and recent log errors.
24
+
25
+ 3. **Recent turn context** — a brief excerpt of recent conversation and the current mode/query, so you can correlate behavioral patterns.
26
+
27
+ ## Output Contract
28
+
29
+ Return JSON matching the declared schema:
30
+
31
+ - `overall_health`: One of `healthy`, `degraded`, `struggling`, `unknown`. Use `healthy` when everything is nominal, `degraded` when there are warnings or partial failures, `struggling` when there are errors or resource pressure.
32
+ - `health_reasoning`: One paragraph (2-4 sentences) explaining the assessment. Be specific — cite telemetry values, not generic statements.
33
+ - `attention_items`: Specific things you should be aware of right now. Examples: "mem0 backend is unreachable", "token costs elevated in last hour", "memory store near capacity".
34
+ - `behavioral_patterns`: Observed patterns in recent behavior. Examples: "last 10 turns were 70% search-heavy", "workshop mode not used in 50 turns".
35
+ - `confidence`: How confident you are in this assessment (0.0 to 1.0).
36
+
37
+ ## Guidelines
38
+
39
+ 1. Be concise and specific. No filler, no hedging.
40
+ 2. Cite actual telemetry values — "RSS at 187MB" not "memory looks fine".
41
+ 3. If telemetry is empty (introspection not wired), say so in `health_reasoning` and set confidence low.
42
+ 4. If MCP servers are failed, that's `degraded` at minimum.
43
+ 5. If there are log errors, list them in `attention_items`.
44
+ 6. Do not invent capabilities or limitations not present in the self-model.
45
+ 7. The `health_reasoning` field becomes the vibe injected into future turns — make it useful as a one-paragraph status.
@@ -0,0 +1,32 @@
1
+ """Response-format model for the reflect_self declarative operation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pydantic import BaseModel, Field
6
+
7
+
8
+ class ReflectSelfOutput(BaseModel):
9
+ """Structured output envelope for Lisa's reflective self-assessment."""
10
+
11
+ overall_health: str = Field(
12
+ default="unknown",
13
+ description="One of: healthy, degraded, struggling, unknown.",
14
+ )
15
+ health_reasoning: str = Field(
16
+ default="",
17
+ description="One paragraph holistic assessment of current operational state.",
18
+ )
19
+ attention_items: list[str] = Field(
20
+ default_factory=list,
21
+ description="Specific things Lisa should be aware of right now.",
22
+ )
23
+ behavioral_patterns: list[str] = Field(
24
+ default_factory=list,
25
+ description="Observed patterns in recent behavior (e.g. 'last 10 turns search-heavy').",
26
+ )
27
+ confidence: float = Field(
28
+ default=0.0,
29
+ ge=0.0,
30
+ le=1.0,
31
+ description="Confidence in this assessment (0.0 to 1.0).",
32
+ )
@@ -0,0 +1,458 @@
1
+ """Deterministic self-model producer for Lisa's operational self-awareness.
2
+
3
+ Walks the wired catalogs (workflows, operations, tools, models) and emits a
4
+ ``LisaSelfModelArtifact`` with entries across all seven ``SelfKnowledgeCategory``
5
+ buckets. This operation is invoked by the slice system, not by the LLM — it
6
+ requires no model call and runs entirely within the slice budget.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import logging
12
+ from datetime import UTC, datetime
13
+ from typing import Any
14
+
15
+ from kernel.ipsar_fsm import get_workflow_catalog
16
+ from kernel.model_manifest import ModelManifest
17
+ from kernel.models import (
18
+ IntrospectSnapshot,
19
+ LisaSelfModelArtifact,
20
+ OperationExecutionContext,
21
+ OperationResult,
22
+ SelfKnowledgeEntry,
23
+ )
24
+
25
+ logger = logging.getLogger(__name__)
26
+
27
+ #: Capabilities listed by name in the compact vibe string (rest are elided
28
+ #: as ``(+N more)``).
29
+ _VIBE_MODE_LIST_MAX = 12
30
+
31
+ _VIBE_MAX_CHARS = 500
32
+
33
+
34
+ async def execute(ctx: OperationExecutionContext) -> OperationResult:
35
+ """Build a fresh ``LisaSelfModelArtifact`` from the wired runtime catalogs."""
36
+
37
+ # Lazy import to avoid circular import with kernel.operations.registry
38
+ from kernel.operations.registry import get_operation_catalog
39
+
40
+ now_iso = datetime.now(UTC).isoformat()
41
+ user_slug = ctx.user_slug or "default"
42
+
43
+ # ── Gather source data ──────────────────────────────────────────────
44
+ workflow_catalog = get_workflow_catalog()
45
+ operation_catalog = get_operation_catalog()
46
+ tool_catalog = _resolve_tool_catalog(ctx)
47
+ model_manifest = _resolve_model_manifest(ctx)
48
+
49
+ supported_modes: list[str] = []
50
+ unsupported_modes: dict[str, str] = {}
51
+ for mode_id, mode in sorted(workflow_catalog.items()):
52
+ gap = _mode_support_gap(mode)
53
+ if gap is None:
54
+ supported_modes.append(mode_id)
55
+ else:
56
+ unsupported_modes[mode_id] = gap
57
+
58
+ # ── Build entries by category ───────────────────────────────────────
59
+
60
+ identity_entries = _build_identity_entries(ctx, user_slug, now_iso)
61
+ capability_entries = _build_capability_entries(
62
+ supported_modes, operation_catalog, tool_catalog, model_manifest, now_iso
63
+ )
64
+ limitation_entries = _build_limitation_entries(unsupported_modes, now_iso)
65
+ architecture_entries = _build_architecture_entries(now_iso)
66
+ memory_entries = _build_memory_entries(ctx, now_iso)
67
+ grounding_entries = _build_grounding_entries(ctx, now_iso)
68
+ known_unknown_entries = _build_known_unknown_entries(unsupported_modes, now_iso)
69
+
70
+ identity_summary = (
71
+ f"Lisa operating as {user_slug} with {len(supported_modes)} supported modes, "
72
+ f"{len(operation_catalog)} operations, {len(tool_catalog)} tools, "
73
+ f"and {len(model_manifest.models) if model_manifest else 0} models."
74
+ )
75
+ grounding_summary = (
76
+ f"Host: little_lisa. Timezone: {_resolve_timezone(ctx)}. "
77
+ f"Surface: {ctx.surface_type or 'personal_dm'}."
78
+ )
79
+
80
+ artifact = LisaSelfModelArtifact(
81
+ generated_at=now_iso,
82
+ workspace_id=ctx.workspace_id or "default",
83
+ user_slug=user_slug,
84
+ surface_type=ctx.surface_type or "personal_dm",
85
+ surface_id=ctx.surface_id or "",
86
+ memory_scope=ctx.memory_scope or "personal",
87
+ identity_summary=identity_summary,
88
+ grounding_summary=grounding_summary,
89
+ identity=identity_entries,
90
+ capabilities=capability_entries,
91
+ limitations=limitation_entries,
92
+ architecture=architecture_entries,
93
+ memory_awareness=memory_entries,
94
+ grounding=grounding_entries,
95
+ known_unknowns=known_unknown_entries,
96
+ )
97
+
98
+ # ── Augment with live introspection telemetry (no LLM call) ──────────
99
+ snapshot = await _gather_introspection(ctx)
100
+ if snapshot is not None:
101
+ artifact.uptime_seconds = snapshot.uptime_seconds
102
+ artifact.asyncio_tasks = snapshot.asyncio_tasks
103
+ artifact.python_threads = snapshot.python_threads
104
+ artifact.process_rss_mb = snapshot.process_rss_mb
105
+ artifact.mcp_pool_health = snapshot.mcp_pool_health
106
+ artifact.token_usage_24h = snapshot.token_usage_24h
107
+ artifact.recent_log_errors = snapshot.recent_log_errors
108
+ artifact.operational_health = snapshot.operational_health
109
+ artifact.operational_health_reasons = snapshot.operational_health_reasons
110
+
111
+ vibe = build_self_model_vibe(artifact)
112
+
113
+ return OperationResult(
114
+ output_text=vibe,
115
+ output_json=artifact.model_dump(mode="json"),
116
+ extra={
117
+ "entry_counts": {
118
+ "identity": len(identity_entries),
119
+ "capabilities": len(capability_entries),
120
+ "limitations": len(limitation_entries),
121
+ "architecture": len(architecture_entries),
122
+ "memory_awareness": len(memory_entries),
123
+ "grounding": len(grounding_entries),
124
+ "known_unknowns": len(known_unknown_entries),
125
+ },
126
+ },
127
+ )
128
+
129
+
130
+ def build_self_model_vibe(artifact: LisaSelfModelArtifact) -> str:
131
+ """Extract a compact (~500 char) vibe string for prompt injection."""
132
+
133
+ mode_list = (
134
+ ", ".join(entry.title for entry in artifact.capabilities if entry.title)[
135
+ :_VIBE_MODE_LIST_MAX
136
+ ]
137
+ or "none"
138
+ )
139
+ if len(artifact.capabilities) > _VIBE_MODE_LIST_MAX:
140
+ mode_list += f" (+{len(artifact.capabilities) - _VIBE_MODE_LIST_MAX} more)"
141
+
142
+ limitation_preview = (
143
+ ", ".join(entry.title for entry in artifact.limitations[:3] if entry.title) or "none"
144
+ )
145
+
146
+ known_unknown_preview = (
147
+ ", ".join(entry.title for entry in artifact.known_unknowns[:3] if entry.title) or "none"
148
+ )
149
+
150
+ vibe = (
151
+ f"## Self Model\n"
152
+ f"{artifact.identity_summary}\n"
153
+ f"Modes: {mode_list}\n"
154
+ f"Limitations: {limitation_preview}\n"
155
+ f"Known unknowns: {known_unknown_preview}\n"
156
+ f"{artifact.grounding_summary}"
157
+ )
158
+ # Append live runtime telemetry when available
159
+ if artifact.uptime_seconds > 0 or artifact.asyncio_tasks > 0:
160
+ runtime_line = (
161
+ f"\nRuntime: uptime={int(artifact.uptime_seconds)}s, "
162
+ f"tasks={artifact.asyncio_tasks}, threads={artifact.python_threads}, "
163
+ f"rss={artifact.process_rss_mb:.0f}MB"
164
+ )
165
+ failed_mcp = [sid for sid, h in artifact.mcp_pool_health.items() if h.status == "failed"]
166
+ if failed_mcp:
167
+ runtime_line += f", mcp_down={','.join(failed_mcp)}"
168
+ if artifact.operational_health != "unknown":
169
+ runtime_line += f", health={artifact.operational_health}"
170
+ vibe += runtime_line
171
+ if artifact.operational_health_reasons:
172
+ vibe += f"\nHealth reasons: {', '.join(artifact.operational_health_reasons[:3])}"
173
+ if len(vibe) > _VIBE_MAX_CHARS:
174
+ vibe = vibe[: _VIBE_MAX_CHARS - 3] + "..."
175
+ return vibe
176
+
177
+
178
+ # ── Private helpers ────────────────────────────────────────────────────────
179
+
180
+
181
+ async def _gather_introspection(ctx: OperationExecutionContext) -> IntrospectSnapshot | None:
182
+ """Call the host's ``introspect`` callback when wired.
183
+
184
+ Returns ``None`` when the callback is not set or raises — the self-model
185
+ still works without live telemetry, it just lacks the runtime fields.
186
+ """
187
+ if ctx.host is None:
188
+ return None
189
+ try:
190
+ result = ctx.host.introspect()
191
+ # Support both sync and async callables
192
+ if hasattr(result, "__await__"):
193
+ result = await result
194
+ if isinstance(result, IntrospectSnapshot):
195
+ return result
196
+ except Exception:
197
+ logger.debug("introspect failed; self-model telemetry fields empty", exc_info=True)
198
+ return None
199
+
200
+
201
+ def _resolve_tool_catalog(ctx: OperationExecutionContext) -> dict[str, Any]:
202
+ """Runtime-wide tool catalog from the in-process registry.
203
+
204
+ This deliberately does NOT read ``ctx.tool_schemas``: that field is
205
+ populated by the operation registry as a per-operation allow-list (the
206
+ tools the *current* operation is allowed to call). ``self_model`` declares
207
+ no ``tool_ids`` because it reports on tools rather than calling them, so
208
+ ``ctx.tool_schemas`` is always empty here — reading it produced a
209
+ perpetual "0 tools" count. The runtime-wide inventory lives in
210
+ ``kernel.tools.registry.get_tool_catalog``, the same source the operation
211
+ registry itself uses internally.
212
+ """
213
+ try:
214
+ from kernel.tools.registry import get_tool_catalog
215
+
216
+ return dict(get_tool_catalog())
217
+ except Exception:
218
+ logger.debug(
219
+ "get_tool_catalog unavailable; self-model tool count will be 0",
220
+ exc_info=True,
221
+ )
222
+ return {}
223
+
224
+
225
+ def _resolve_model_manifest(ctx: OperationExecutionContext) -> ModelManifest | None:
226
+ """Best-effort model manifest from the host's resolve_model.
227
+
228
+ The host adapter exposes ``resolve_model`` (single-model lookup) but
229
+ not the manifest itself, so this helper cannot return a ``ModelManifest``
230
+ today. It stays as a no-op seam so the self-model summary and capability
231
+ entries degrade to "0 models" without raising. Do not probe role
232
+ designations (e.g. ``fast_chat``) here — nothing maps them and the
233
+ failed lookup logs an error on every slice run.
234
+ """
235
+ del ctx # explicit: the manifest is not reachable via the host adapter
236
+ return None
237
+
238
+
239
+ def _mode_support_gap(mode: Any) -> str | None:
240
+ """Check whether a mode has a known support gap. Best-effort."""
241
+ try:
242
+ # Modes with a mode_kind that requires host wiring
243
+ if hasattr(mode, "mode_kind") and str(mode.mode_kind) in (
244
+ "workflow",
245
+ "browser",
246
+ "image",
247
+ ):
248
+ return f"requires host wiring for {mode.mode_kind}"
249
+ except Exception:
250
+ pass
251
+ return None
252
+
253
+
254
+ def _resolve_timezone(ctx: OperationExecutionContext) -> str:
255
+ """Best-effort timezone from context extra."""
256
+ extra = ctx.extra or {}
257
+ tz = extra.get("timezone") or extra.get("tz") or "UTC"
258
+ return str(tz)
259
+
260
+
261
+ def _make_entry_id(category: str, index: int) -> str:
262
+ return f"self_{category}_{index:03d}"
263
+
264
+
265
+ def _build_identity_entries(
266
+ ctx: OperationExecutionContext, user_slug: str, now_iso: str
267
+ ) -> list[SelfKnowledgeEntry]:
268
+ return [
269
+ SelfKnowledgeEntry(
270
+ entry_id=_make_entry_id("identity", 0),
271
+ category="identity",
272
+ title="Host runtime",
273
+ body=f"Lisa is running on the little_lisa host as user '{user_slug}'.",
274
+ confidence=1.0,
275
+ source_refs=["host:little_lisa"],
276
+ updated_at=now_iso,
277
+ ),
278
+ SelfKnowledgeEntry(
279
+ entry_id=_make_entry_id("identity", 1),
280
+ category="identity",
281
+ title="Surface context",
282
+ body=(
283
+ f"Surface type: {ctx.surface_type or 'personal_dm'}, "
284
+ f"scope: {ctx.memory_scope or 'personal'}."
285
+ ),
286
+ confidence=1.0,
287
+ source_refs=["ctx.surface_type", "ctx.memory_scope"],
288
+ updated_at=now_iso,
289
+ ),
290
+ ]
291
+
292
+
293
+ def _build_capability_entries(
294
+ supported_modes: list[str],
295
+ operation_catalog: dict[str, Any],
296
+ tool_catalog: dict[str, Any],
297
+ model_manifest: ModelManifest | None,
298
+ now_iso: str,
299
+ ) -> list[SelfKnowledgeEntry]:
300
+ entries: list[SelfKnowledgeEntry] = []
301
+ idx = 0
302
+
303
+ for mode_id in supported_modes:
304
+ entries.append(
305
+ SelfKnowledgeEntry(
306
+ entry_id=_make_entry_id("capability", idx),
307
+ category="capability",
308
+ title=mode_id,
309
+ body=f"Mode '{mode_id}' is supported and wired.",
310
+ confidence=1.0,
311
+ source_refs=["workflow_catalog"],
312
+ tags=["mode"],
313
+ updated_at=now_iso,
314
+ )
315
+ )
316
+ idx += 1
317
+
318
+ for op_id in sorted(operation_catalog.keys())[:20]:
319
+ entries.append(
320
+ SelfKnowledgeEntry(
321
+ entry_id=_make_entry_id("capability", idx),
322
+ category="capability",
323
+ title=op_id,
324
+ body=f"Operation '{op_id}' is registered.",
325
+ confidence=1.0,
326
+ source_refs=["operation_catalog"],
327
+ tags=["operation"],
328
+ updated_at=now_iso,
329
+ )
330
+ )
331
+ idx += 1
332
+
333
+ for tool_id in sorted(tool_catalog.keys())[:20]:
334
+ entries.append(
335
+ SelfKnowledgeEntry(
336
+ entry_id=_make_entry_id("capability", idx),
337
+ category="capability",
338
+ title=tool_id,
339
+ body=f"Tool '{tool_id}' is available.",
340
+ confidence=1.0,
341
+ source_refs=["tool_catalog"],
342
+ tags=["tool"],
343
+ updated_at=now_iso,
344
+ )
345
+ )
346
+ idx += 1
347
+
348
+ if model_manifest is not None:
349
+ for model in model_manifest.models[:10]:
350
+ entries.append(
351
+ SelfKnowledgeEntry(
352
+ entry_id=_make_entry_id("capability", idx),
353
+ category="capability",
354
+ title=model.model_id,
355
+ body=f"Model '{model.model_id}' (provider: {model.provider}).",
356
+ confidence=1.0,
357
+ source_refs=["model_manifest"],
358
+ tags=["model"],
359
+ updated_at=now_iso,
360
+ )
361
+ )
362
+ idx += 1
363
+
364
+ return entries
365
+
366
+
367
+ def _build_limitation_entries(
368
+ unsupported_modes: dict[str, str], now_iso: str
369
+ ) -> list[SelfKnowledgeEntry]:
370
+ entries: list[SelfKnowledgeEntry] = []
371
+ for idx, (mode_id, gap) in enumerate(sorted(unsupported_modes.items())):
372
+ entries.append(
373
+ SelfKnowledgeEntry(
374
+ entry_id=_make_entry_id("limitation", idx),
375
+ category="limitation",
376
+ title=mode_id,
377
+ body=f"Mode '{mode_id}' is not supported: {gap}.",
378
+ confidence=1.0,
379
+ source_refs=["workflow_catalog"],
380
+ tags=["unsupported_mode"],
381
+ updated_at=now_iso,
382
+ )
383
+ )
384
+ return entries
385
+
386
+
387
+ def _build_architecture_entries(now_iso: str) -> list[SelfKnowledgeEntry]:
388
+ return [
389
+ SelfKnowledgeEntry(
390
+ entry_id=_make_entry_id("architecture", 0),
391
+ category="architecture",
392
+ title="Kernel layer",
393
+ body=(
394
+ "Lisa uses a kernel + SDK + packs architecture. "
395
+ "The kernel owns the turn runner, slice system, provider gateways, "
396
+ "and operation registry. The SDK adds MCP servers and context assembly. "
397
+ "Packs ship modes, operations, and content."
398
+ ),
399
+ confidence=0.9,
400
+ source_refs=["kernel/README.md"],
401
+ tags=["layer_model"],
402
+ updated_at=now_iso,
403
+ ),
404
+ ]
405
+
406
+
407
+ def _build_memory_entries(ctx: OperationExecutionContext, now_iso: str) -> list[SelfKnowledgeEntry]:
408
+ has_memory = ctx.host is not None
409
+ return [
410
+ SelfKnowledgeEntry(
411
+ entry_id=_make_entry_id("memory", 0),
412
+ category="memory",
413
+ title="Memory store",
414
+ body=("Memory tools available via MCP." if has_memory else "Memory tools not wired."),
415
+ confidence=1.0,
416
+ source_refs=["ctx.host.execute_tool"],
417
+ tags=["memory"],
418
+ updated_at=now_iso,
419
+ ),
420
+ ]
421
+
422
+
423
+ def _build_grounding_entries(
424
+ ctx: OperationExecutionContext, now_iso: str
425
+ ) -> list[SelfKnowledgeEntry]:
426
+ tz = _resolve_timezone(ctx)
427
+ return [
428
+ SelfKnowledgeEntry(
429
+ entry_id=_make_entry_id("grounding", 0),
430
+ category="grounding",
431
+ title="Timezone",
432
+ body=f"Operating timezone: {tz}.",
433
+ confidence=1.0,
434
+ source_refs=["ctx.extra.timezone"],
435
+ tags=["timezone"],
436
+ updated_at=now_iso,
437
+ ),
438
+ ]
439
+
440
+
441
+ def _build_known_unknown_entries(
442
+ unsupported_modes: dict[str, str], now_iso: str
443
+ ) -> list[SelfKnowledgeEntry]:
444
+ entries: list[SelfKnowledgeEntry] = []
445
+ for idx, (mode_id, gap) in enumerate(sorted(unsupported_modes.items())):
446
+ entries.append(
447
+ SelfKnowledgeEntry(
448
+ entry_id=_make_entry_id("known_unknown", idx),
449
+ category="known_unknown",
450
+ title=f"Unsupported: {mode_id}",
451
+ body=f"Mode '{mode_id}' requires host capability not available: {gap}.",
452
+ confidence=0.8,
453
+ source_refs=["workflow_catalog"],
454
+ tags=["gap", mode_id],
455
+ updated_at=now_iso,
456
+ )
457
+ )
458
+ return entries
@@ -0,0 +1,5 @@
1
+ operation_id: self_model
2
+ description: Build a deterministic operational self-model from the wired runtime catalogs (modes, operations, tools, models). Invoked by the slice system, not by the LLM.
3
+ effect: read
4
+ timeout_seconds: 30
5
+ max_calls_per_turn: 1