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.
- lisa_pack_introspect-0.1.0/.gitignore +33 -0
- lisa_pack_introspect-0.1.0/PKG-INFO +10 -0
- lisa_pack_introspect-0.1.0/pyproject.toml +30 -0
- lisa_pack_introspect-0.1.0/src/sdk/packs/introspect/__init__.py +29 -0
- lisa_pack_introspect-0.1.0/src/sdk/packs/introspect/operations/__init__.py +1 -0
- lisa_pack_introspect-0.1.0/src/sdk/packs/introspect/operations/self_model/__init__.py +1 -0
- lisa_pack_introspect-0.1.0/src/sdk/packs/introspect/operations/self_model/reflect_self.operation.md +45 -0
- lisa_pack_introspect-0.1.0/src/sdk/packs/introspect/operations/self_model/reflect_self.py +32 -0
- lisa_pack_introspect-0.1.0/src/sdk/packs/introspect/operations/self_model/self_model.py +458 -0
- lisa_pack_introspect-0.1.0/src/sdk/packs/introspect/operations/self_model/self_model.yml +5 -0
|
@@ -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."""
|
lisa_pack_introspect-0.1.0/src/sdk/packs/introspect/operations/self_model/reflect_self.operation.md
ADDED
|
@@ -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
|