claude-smart 0.2.46 → 0.2.47
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.
- package/.claude-plugin/marketplace.json +1 -1
- package/README.md +19 -11
- package/bin/claude-smart.js +290 -68
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/README.md +11 -10
- package/plugin/dashboard/app/layout.tsx +20 -0
- package/plugin/opencode/dist/server.mjs +76 -2
- package/plugin/opencode/server.mts +79 -2
- package/plugin/pyproject.toml +6 -2
- package/plugin/scripts/smart-install.sh +7 -1
- package/plugin/src/claude_smart/cli.py +210 -22
- package/plugin/src/claude_smart/context_format.py +9 -9
- package/plugin/src/claude_smart/cs_cite.py +66 -19
- package/plugin/uv.lock +5 -5
- package/plugin/vendor/reflexio/README.md +3 -3
- package/plugin/vendor/reflexio/pyproject.toml +1 -1
- package/plugin/vendor/reflexio/reflexio/README.md +3 -1
- package/plugin/vendor/reflexio/reflexio/cli/bootstrap_config.py +1 -1
- package/plugin/vendor/reflexio/reflexio/cli/commands/setup_cmd.py +2 -2
- package/plugin/vendor/reflexio/reflexio/cli/utils.py +44 -1
- package/plugin/vendor/reflexio/reflexio/client/client.py +97 -0
- package/plugin/vendor/reflexio/reflexio/lib/_agent_playbook.py +8 -0
- package/plugin/vendor/reflexio/reflexio/lib/_base.py +15 -0
- package/plugin/vendor/reflexio/reflexio/lib/_generation.py +9 -8
- package/plugin/vendor/reflexio/reflexio/lib/_profiles.py +27 -16
- package/plugin/vendor/reflexio/reflexio/lib/_search.py +23 -5
- package/plugin/vendor/reflexio/reflexio/lib/_user_playbook.py +9 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/__init__.py +1 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/governance.py +117 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/retriever_schema.py +45 -3
- package/plugin/vendor/reflexio/reflexio/models/config_schema.py +16 -0
- package/plugin/vendor/reflexio/reflexio/server/README.md +14 -2
- package/plugin/vendor/reflexio/reflexio/server/api.py +176 -29
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/request_context.py +1 -1
- package/plugin/vendor/reflexio/reflexio/server/{_auth.py → auth.py} +2 -0
- package/plugin/vendor/reflexio/reflexio/server/extensions.py +213 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/model_defaults.py +4 -4
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/claude_code_provider.py +1 -1
- package/plugin/vendor/reflexio/reflexio/server/llm/rerank/cross_encoder_reranker.py +12 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.4.0.prompt.md +14 -2
- package/plugin/vendor/reflexio/reflexio/server/services/README.md +3 -1
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_worker.py +8 -0
- package/plugin/vendor/reflexio/reflexio/server/services/governance/config.py +52 -0
- package/plugin/vendor/reflexio/reflexio/server/services/governance/service.py +378 -0
- package/plugin/vendor/reflexio/reflexio/server/services/governance/subject_refs.py +34 -0
- package/plugin/vendor/reflexio/reflexio/server/services/lineage/gc_scheduler.py +45 -19
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/README.md +9 -1
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/aggregation_prompt_processing.py +100 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/aggregator.py +129 -111
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/service.py +9 -9
- package/plugin/vendor/reflexio/reflexio/server/services/profile/components/extractor.py +3 -2
- package/plugin/vendor/reflexio/reflexio/server/services/retrieval/recency.py +211 -0
- package/plugin/vendor/reflexio/reflexio/server/services/retrieval/relevance_floor.py +29 -13
- package/plugin/vendor/reflexio/reflexio/server/services/storage/error.py +4 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/governance_validation.py +681 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/__init__.py +14 -2
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_base.py +2 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_extras.py +49 -19
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_governance.py +1965 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_lineage.py +5 -3
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_playbook.py +1 -2133
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_profiles.py +262 -107
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_requests.py +73 -33
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/__init__.py +13 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_agent.py +952 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_eval_results.py +189 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_optimization.py +247 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_source_linkage.py +145 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_user.py +838 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/__init__.py +19 -3
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_extras.py +4 -4
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_governance.py +148 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_playbook.py +0 -909
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_profiles.py +13 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_requests.py +2 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/__init__.py +13 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_agent.py +365 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_eval_results.py +124 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_optimization.py +85 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_source_linkage.py +47 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_user.py +333 -0
- package/plugin/vendor/reflexio/reflexio/server/services/unified_search_service.py +153 -12
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/user_detail_stripping.py +0 -84
|
@@ -6,7 +6,10 @@ import time
|
|
|
6
6
|
from collections.abc import Callable
|
|
7
7
|
from concurrent.futures import ThreadPoolExecutor
|
|
8
8
|
from datetime import UTC, datetime
|
|
9
|
-
from typing import Any
|
|
9
|
+
from typing import TYPE_CHECKING, Any
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from reflexio.server.extensions import AppContext, CapabilityRegistry
|
|
10
13
|
|
|
11
14
|
from anyio.to_thread import current_default_thread_limiter
|
|
12
15
|
from fastapi import (
|
|
@@ -162,12 +165,6 @@ from reflexio.models.config_schema import (
|
|
|
162
165
|
SINGLETON_AGENT_SUCCESS_EVALUATION_NAME,
|
|
163
166
|
Config,
|
|
164
167
|
)
|
|
165
|
-
from reflexio.server._auth import (
|
|
166
|
-
DEFAULT_ORG_ID,
|
|
167
|
-
default_billing_gate,
|
|
168
|
-
default_get_caller_type,
|
|
169
|
-
default_get_org_id,
|
|
170
|
-
)
|
|
171
168
|
from reflexio.server.api_endpoints import (
|
|
172
169
|
account_api,
|
|
173
170
|
health_api,
|
|
@@ -175,6 +172,12 @@ from reflexio.server.api_endpoints import (
|
|
|
175
172
|
publisher_api,
|
|
176
173
|
stall_state_api,
|
|
177
174
|
)
|
|
175
|
+
from reflexio.server.auth import (
|
|
176
|
+
DEFAULT_ORG_ID,
|
|
177
|
+
default_billing_gate,
|
|
178
|
+
default_get_caller_type,
|
|
179
|
+
default_get_org_id,
|
|
180
|
+
)
|
|
178
181
|
from reflexio.server.cache.reflexio_cache import (
|
|
179
182
|
get_reflexio,
|
|
180
183
|
invalidate_reflexio_cache,
|
|
@@ -1845,6 +1848,12 @@ def get_all_profiles(
|
|
|
1845
1848
|
limit: int = 100,
|
|
1846
1849
|
status_filter: str | None = None,
|
|
1847
1850
|
profile_id: str | None = None,
|
|
1851
|
+
user_id: str | None = None,
|
|
1852
|
+
query: str | None = None,
|
|
1853
|
+
source: str | None = None,
|
|
1854
|
+
profile_time_to_live: str | None = None,
|
|
1855
|
+
start_time: int | None = None,
|
|
1856
|
+
end_time: int | None = None,
|
|
1848
1857
|
include_tombstones: bool = False,
|
|
1849
1858
|
org_id: str = Depends(default_get_org_id),
|
|
1850
1859
|
) -> GetProfilesViewResponse:
|
|
@@ -1854,6 +1863,12 @@ def get_all_profiles(
|
|
|
1854
1863
|
limit (int, optional): Maximum number of profiles to return. Defaults to 100.
|
|
1855
1864
|
status_filter (str, optional): Filter by profile status. Can be "current", "pending", or "archived".
|
|
1856
1865
|
profile_id (str, optional): Exact profile ID to retrieve.
|
|
1866
|
+
user_id (str, optional): Exact user ID to filter by.
|
|
1867
|
+
query (str, optional): Case-insensitive text filter across visible fields.
|
|
1868
|
+
source (str, optional): Exact profile source to filter by.
|
|
1869
|
+
profile_time_to_live (str, optional): Exact TTL value to filter by.
|
|
1870
|
+
start_time (int, optional): Minimum last-modified epoch seconds.
|
|
1871
|
+
end_time (int, optional): Maximum last-modified epoch seconds.
|
|
1857
1872
|
include_tombstones (bool, optional): Include merged/superseded rows when
|
|
1858
1873
|
looking up a specific profile_id.
|
|
1859
1874
|
org_id (str): Organization ID
|
|
@@ -1863,7 +1878,7 @@ def get_all_profiles(
|
|
|
1863
1878
|
"""
|
|
1864
1879
|
reflexio = get_reflexio(org_id=org_id)
|
|
1865
1880
|
|
|
1866
|
-
if profile_id:
|
|
1881
|
+
if profile_id and include_tombstones:
|
|
1867
1882
|
storage = reflexio.request_context.storage
|
|
1868
1883
|
if storage is None:
|
|
1869
1884
|
return GetProfilesViewResponse(
|
|
@@ -1890,7 +1905,17 @@ def get_all_profiles(
|
|
|
1890
1905
|
elif status_filter == "archived":
|
|
1891
1906
|
status_filter_list = [Status.ARCHIVED]
|
|
1892
1907
|
|
|
1893
|
-
response = reflexio.get_all_profiles(
|
|
1908
|
+
response = reflexio.get_all_profiles(
|
|
1909
|
+
limit=limit,
|
|
1910
|
+
status_filter=status_filter_list, # type: ignore[reportArgumentType]
|
|
1911
|
+
user_id=user_id,
|
|
1912
|
+
profile_id=profile_id,
|
|
1913
|
+
query=query,
|
|
1914
|
+
source=source,
|
|
1915
|
+
profile_time_to_live=profile_time_to_live,
|
|
1916
|
+
start_time=start_time,
|
|
1917
|
+
end_time=end_time,
|
|
1918
|
+
)
|
|
1894
1919
|
return GetProfilesViewResponse(
|
|
1895
1920
|
success=response.success,
|
|
1896
1921
|
user_profiles=[to_profile_view(p) for p in response.user_profiles],
|
|
@@ -3302,6 +3327,97 @@ def _add_openapi_security(app: FastAPI) -> None:
|
|
|
3302
3327
|
app.openapi = custom_openapi # type: ignore[method-assign]
|
|
3303
3328
|
|
|
3304
3329
|
|
|
3330
|
+
def _resolve_lifespan_org_id(get_org_id: Callable[..., str] | None) -> str:
|
|
3331
|
+
"""Resolve the bootstrap org ID for lifespan schedulers without a request context.
|
|
3332
|
+
|
|
3333
|
+
Args:
|
|
3334
|
+
get_org_id (Callable[..., str] | None): Custom org-ID dependency, or None for
|
|
3335
|
+
the default single-tenant mode.
|
|
3336
|
+
|
|
3337
|
+
Returns:
|
|
3338
|
+
str: The resolved org ID string.
|
|
3339
|
+
"""
|
|
3340
|
+
from reflexio.server.auth import default_get_org_id
|
|
3341
|
+
|
|
3342
|
+
if get_org_id is None:
|
|
3343
|
+
return default_get_org_id()
|
|
3344
|
+
try:
|
|
3345
|
+
signature = inspect.signature(get_org_id)
|
|
3346
|
+
except (TypeError, ValueError):
|
|
3347
|
+
return default_get_org_id()
|
|
3348
|
+
if signature.parameters:
|
|
3349
|
+
return default_get_org_id()
|
|
3350
|
+
try:
|
|
3351
|
+
return str(get_org_id())
|
|
3352
|
+
except Exception:
|
|
3353
|
+
logger.exception("Failed to resolve lifespan org_id; using default org")
|
|
3354
|
+
return default_get_org_id()
|
|
3355
|
+
|
|
3356
|
+
|
|
3357
|
+
def _wire_capabilities(
|
|
3358
|
+
app: FastAPI,
|
|
3359
|
+
capabilities: "CapabilityRegistry | None",
|
|
3360
|
+
mount_data_plane: bool,
|
|
3361
|
+
additional_routers: list[APIRouter] | None = None,
|
|
3362
|
+
) -> None:
|
|
3363
|
+
"""Wire capability routers, services, and hooks into the app at construction time.
|
|
3364
|
+
|
|
3365
|
+
No-op when ``capabilities`` is None.
|
|
3366
|
+
|
|
3367
|
+
The deployment role passed to each capability's ``routers(role)`` is
|
|
3368
|
+
sourced from ``capabilities.role`` when set (threaded in by the enterprise
|
|
3369
|
+
composition root via ``build_registry(deployment_role())``). When
|
|
3370
|
+
``capabilities.role`` is ``None`` the role falls back to the
|
|
3371
|
+
``mount_data_plane`` derivation (``"all"`` when True, ``"control-plane"``
|
|
3372
|
+
when False), preserving the existing OSS-only behaviour.
|
|
3373
|
+
|
|
3374
|
+
Args:
|
|
3375
|
+
app (FastAPI): The application instance to wire into.
|
|
3376
|
+
capabilities (CapabilityRegistry | None): Capabilities to install, or None.
|
|
3377
|
+
mount_data_plane (bool): Whether the data-plane role is active.
|
|
3378
|
+
additional_routers (list[APIRouter] | None): Routers already mounted via
|
|
3379
|
+
``additional_routers``; used to detect and reject double-mounts at boot.
|
|
3380
|
+
|
|
3381
|
+
Raises:
|
|
3382
|
+
ValueError: If a capability's router object is also present in
|
|
3383
|
+
``additional_routers`` — each router must be mounted exactly once.
|
|
3384
|
+
"""
|
|
3385
|
+
if capabilities is None:
|
|
3386
|
+
return
|
|
3387
|
+
from reflexio.server.auth import default_billing_gate
|
|
3388
|
+
from reflexio.server.extensions import HookRegistry
|
|
3389
|
+
|
|
3390
|
+
if capabilities.role is not None:
|
|
3391
|
+
role = capabilities.role
|
|
3392
|
+
else:
|
|
3393
|
+
role = "all" if mount_data_plane else "control-plane"
|
|
3394
|
+
if capabilities.configurator_class is not None:
|
|
3395
|
+
from reflexio.server.services.configurator.configurator import (
|
|
3396
|
+
set_configurator_class,
|
|
3397
|
+
)
|
|
3398
|
+
|
|
3399
|
+
set_configurator_class(capabilities.configurator_class)
|
|
3400
|
+
if capabilities.billing_gate is not None:
|
|
3401
|
+
for line in ("application", "learnings_generated"):
|
|
3402
|
+
app.dependency_overrides[default_billing_gate(line)] = (
|
|
3403
|
+
capabilities.billing_gate(line)
|
|
3404
|
+
)
|
|
3405
|
+
# HookRegistry is a stateless facade: its methods configure process-global
|
|
3406
|
+
# tracer/usage-recorder/retrieval-capture singletons; the object needn't be stored.
|
|
3407
|
+
hooks = HookRegistry()
|
|
3408
|
+
additional = additional_routers or []
|
|
3409
|
+
for cap in capabilities.capabilities:
|
|
3410
|
+
cap.install_services()
|
|
3411
|
+
cap.install_hooks(hooks)
|
|
3412
|
+
for r in cap.routers(role):
|
|
3413
|
+
if any(r is a for a in additional):
|
|
3414
|
+
raise ValueError(
|
|
3415
|
+
f"router for capability {cap.name!r} is mounted both via the "
|
|
3416
|
+
f"registry and additional_routers; mount it exactly once"
|
|
3417
|
+
)
|
|
3418
|
+
app.include_router(r)
|
|
3419
|
+
|
|
3420
|
+
|
|
3305
3421
|
def create_app(
|
|
3306
3422
|
get_org_id: Callable[..., str] | None = None,
|
|
3307
3423
|
additional_routers: list[APIRouter] | None = None,
|
|
@@ -3310,6 +3426,8 @@ def create_app(
|
|
|
3310
3426
|
get_caller_type: Callable[..., str] | None = None,
|
|
3311
3427
|
get_billing_gate: Callable[[str], Callable[..., None]] | None = None,
|
|
3312
3428
|
mount_data_plane: bool = True,
|
|
3429
|
+
capabilities: "CapabilityRegistry | None" = None,
|
|
3430
|
+
app_context_factory: "Callable[[], AppContext] | None" = None,
|
|
3313
3431
|
) -> FastAPI:
|
|
3314
3432
|
"""Factory to create a FastAPI app.
|
|
3315
3433
|
|
|
@@ -3337,19 +3455,40 @@ def create_app(
|
|
|
3337
3455
|
scheduler, while keeping all other scaffolding (middleware, CORS,
|
|
3338
3456
|
auth overrides, OpenAPI security, health, ``/meta/version``,
|
|
3339
3457
|
``additional_routers``).
|
|
3458
|
+
capabilities: Optional registry of enterprise capabilities. When provided,
|
|
3459
|
+
each capability's routers, services, hooks, and lifecycle methods are
|
|
3460
|
+
wired into the app at construction time. Behavior is unchanged when
|
|
3461
|
+
``None``.
|
|
3462
|
+
app_context_factory: Optional callable returning the AppContext passed to
|
|
3463
|
+
each capability's on_startup. When None, an empty AppContext() is used
|
|
3464
|
+
(local OSS / tests). Enterprise binds this to supply self_host_org_id /
|
|
3465
|
+
activated computed during its own startup.
|
|
3340
3466
|
|
|
3341
3467
|
Returns:
|
|
3342
3468
|
Configured FastAPI application.
|
|
3343
|
-
|
|
3469
|
+
|
|
3470
|
+
Raises:
|
|
3471
|
+
ValueError: If both ``get_billing_gate`` and ``capabilities.billing_gate``
|
|
3472
|
+
are provided — pass billing_gate through exactly one path.
|
|
3473
|
+
"""
|
|
3474
|
+
if (
|
|
3475
|
+
get_billing_gate is not None
|
|
3476
|
+
and capabilities is not None
|
|
3477
|
+
and capabilities.billing_gate is not None
|
|
3478
|
+
):
|
|
3479
|
+
raise ValueError(
|
|
3480
|
+
"pass billing_gate via either get_billing_gate or capabilities, not both"
|
|
3481
|
+
)
|
|
3344
3482
|
from collections.abc import AsyncIterator
|
|
3345
3483
|
from contextlib import asynccontextmanager
|
|
3346
3484
|
|
|
3347
|
-
from reflexio.server.
|
|
3485
|
+
from reflexio.server.api_endpoints.request_context import RequestContext
|
|
3486
|
+
from reflexio.server.auth import (
|
|
3348
3487
|
default_billing_gate,
|
|
3349
3488
|
default_get_caller_type,
|
|
3350
3489
|
default_get_org_id,
|
|
3351
3490
|
)
|
|
3352
|
-
from reflexio.server.
|
|
3491
|
+
from reflexio.server.extensions import AppContext
|
|
3353
3492
|
from reflexio.server.llm.model_defaults import validate_llm_availability
|
|
3354
3493
|
from reflexio.server.services.extraction.resume_scheduler import (
|
|
3355
3494
|
maybe_start_resume_scheduler,
|
|
@@ -3358,25 +3497,11 @@ def create_app(
|
|
|
3358
3497
|
maybe_start_lineage_gc,
|
|
3359
3498
|
)
|
|
3360
3499
|
|
|
3361
|
-
def _lifespan_org_id() -> str:
|
|
3362
|
-
if get_org_id is None:
|
|
3363
|
-
return default_get_org_id()
|
|
3364
|
-
try:
|
|
3365
|
-
signature = inspect.signature(get_org_id)
|
|
3366
|
-
except (TypeError, ValueError):
|
|
3367
|
-
return default_get_org_id()
|
|
3368
|
-
if signature.parameters:
|
|
3369
|
-
return default_get_org_id()
|
|
3370
|
-
try:
|
|
3371
|
-
return str(get_org_id())
|
|
3372
|
-
except Exception:
|
|
3373
|
-
logger.exception("Failed to resolve lifespan org_id; using default org")
|
|
3374
|
-
return default_get_org_id()
|
|
3375
|
-
|
|
3376
3500
|
@asynccontextmanager
|
|
3377
3501
|
async def lifespan(app: FastAPI) -> AsyncIterator[None]: # noqa: ARG001
|
|
3378
3502
|
scheduler = None
|
|
3379
3503
|
gc_scheduler = None
|
|
3504
|
+
started_caps: list = []
|
|
3380
3505
|
if mount_data_plane:
|
|
3381
3506
|
log_publish_hardware_capacity()
|
|
3382
3507
|
validate_llm_availability()
|
|
@@ -3387,17 +3512,36 @@ def create_app(
|
|
|
3387
3512
|
# drives a per-org worker with org-scoped claims, so it is not limited
|
|
3388
3513
|
# to the bootstrap org. The bootstrap org is only used to read config
|
|
3389
3514
|
# and to seed cross-org discovery.
|
|
3515
|
+
bootstrap_org_id = _resolve_lifespan_org_id(get_org_id)
|
|
3390
3516
|
scheduler = maybe_start_resume_scheduler(
|
|
3391
3517
|
lambda org_id: RequestContext(org_id=org_id),
|
|
3392
|
-
bootstrap_org_id=
|
|
3518
|
+
bootstrap_org_id=bootstrap_org_id,
|
|
3393
3519
|
)
|
|
3394
3520
|
gc_scheduler = maybe_start_lineage_gc(
|
|
3395
3521
|
lambda org_id: RequestContext(org_id=org_id),
|
|
3396
|
-
bootstrap_org_id=
|
|
3522
|
+
bootstrap_org_id=bootstrap_org_id,
|
|
3397
3523
|
)
|
|
3398
3524
|
try:
|
|
3525
|
+
if capabilities is not None:
|
|
3526
|
+
ctx = (
|
|
3527
|
+
app_context_factory()
|
|
3528
|
+
if app_context_factory is not None
|
|
3529
|
+
else AppContext()
|
|
3530
|
+
)
|
|
3531
|
+
for cap in capabilities.capabilities:
|
|
3532
|
+
await cap.on_startup(ctx)
|
|
3533
|
+
started_caps.append(cap)
|
|
3399
3534
|
yield
|
|
3400
3535
|
finally:
|
|
3536
|
+
for cap in reversed(started_caps):
|
|
3537
|
+
try:
|
|
3538
|
+
await cap.on_shutdown()
|
|
3539
|
+
except Exception:
|
|
3540
|
+
logger.warning(
|
|
3541
|
+
"capability %r on_shutdown raised; continuing cleanup",
|
|
3542
|
+
cap,
|
|
3543
|
+
exc_info=True,
|
|
3544
|
+
)
|
|
3401
3545
|
if scheduler is not None:
|
|
3402
3546
|
scheduler.stop()
|
|
3403
3547
|
if gc_scheduler is not None:
|
|
@@ -3507,6 +3651,9 @@ def create_app(
|
|
|
3507
3651
|
for router in additional_routers or []:
|
|
3508
3652
|
app.include_router(router)
|
|
3509
3653
|
|
|
3654
|
+
# Wire capability routers, services, and hooks (composition-root only)
|
|
3655
|
+
_wire_capabilities(app, capabilities, mount_data_plane, additional_routers)
|
|
3656
|
+
|
|
3510
3657
|
# Health/observability endpoint (per-worker metrics for recycling)
|
|
3511
3658
|
health_api.install(app)
|
|
3512
3659
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from fastapi import Depends
|
|
2
2
|
|
|
3
|
-
from reflexio.server.
|
|
3
|
+
from reflexio.server.auth import default_get_org_id
|
|
4
4
|
from reflexio.server.prompt.prompt_manager import PromptManager
|
|
5
5
|
from reflexio.server.services.configurator.base_configurator import BaseConfigurator
|
|
6
6
|
from reflexio.server.services.configurator.configurator import get_configurator_class
|
|
@@ -5,6 +5,8 @@ Lives in its own module to avoid an import cycle: api endpoints reference
|
|
|
5
5
|
:func:`default_get_org_id` (e.g. via FastAPI ``Depends``) and are themselves
|
|
6
6
|
imported by :mod:`reflexio.server.api`. Putting the dependency here keeps the
|
|
7
7
|
import graph acyclic.
|
|
8
|
+
|
|
9
|
+
Public module path: :mod:`reflexio.server.auth`.
|
|
8
10
|
"""
|
|
9
11
|
|
|
10
12
|
from __future__ import annotations
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"""OSS extension primitives: the process-global capability/service registry.
|
|
2
|
+
|
|
3
|
+
OSS defines these seams; enterprise registers implementations at its composition
|
|
4
|
+
root. OSS never imports reflexio_ext. The service registry is process-global (a
|
|
5
|
+
module dict), mirroring set_configurator_class/get_configurator_class, so it is
|
|
6
|
+
reachable from every RequestContext construction site — HTTP, library
|
|
7
|
+
(lib/_base.py), and background workers — not just app-scoped ones.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from abc import ABC
|
|
13
|
+
from collections.abc import Callable
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
from typing import ClassVar
|
|
16
|
+
|
|
17
|
+
from fastapi import APIRouter
|
|
18
|
+
|
|
19
|
+
from reflexio.server.services.unified_search_service import (
|
|
20
|
+
RetrievalCaptureHook,
|
|
21
|
+
configure_retrieval_capture_hook,
|
|
22
|
+
)
|
|
23
|
+
from reflexio.server.tracing import Tracer, configure_tracer
|
|
24
|
+
from reflexio.server.usage_metrics import (
|
|
25
|
+
UsageEventRecorder,
|
|
26
|
+
configure_usage_event_recorder,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ServiceKey[T]:
|
|
31
|
+
"""Typed handle into the runtime-service registry.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
name (str): Stable identifier, used in error messages and as the dict key.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(self, name: str) -> None:
|
|
38
|
+
self.name = name
|
|
39
|
+
|
|
40
|
+
def __repr__(self) -> str:
|
|
41
|
+
return f"ServiceKey({self.name!r})"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
_services: dict[str, object] = {}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def register_service[T](
|
|
48
|
+
key: ServiceKey[T], value: T, *, override: bool = False
|
|
49
|
+
) -> None:
|
|
50
|
+
"""Register a runtime-service provider under ``key`` (composition root only).
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
key (ServiceKey[T]): The typed key.
|
|
54
|
+
value (T): The provider/value.
|
|
55
|
+
override (bool): Allow replacing an existing registration. Default False.
|
|
56
|
+
|
|
57
|
+
Raises:
|
|
58
|
+
RuntimeError: If ``key`` is already registered and ``override`` is False.
|
|
59
|
+
"""
|
|
60
|
+
if not override and key.name in _services:
|
|
61
|
+
raise RuntimeError(f"service already registered for key {key.name!r}")
|
|
62
|
+
_services[key.name] = value
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def get_service[T](key: ServiceKey[T]) -> T | None:
|
|
66
|
+
"""Return the provider for ``key``, or None if unset (optional providers)."""
|
|
67
|
+
return _services.get(key.name) # type: ignore[return-value]
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def require_service[T](key: ServiceKey[T]) -> T:
|
|
71
|
+
"""Return the provider for ``key``, raising if unset (required providers).
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
key (ServiceKey[T]): The typed key whose provider to retrieve.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
T: The registered provider value.
|
|
78
|
+
|
|
79
|
+
Raises:
|
|
80
|
+
LookupError: If no provider is registered for ``key``.
|
|
81
|
+
"""
|
|
82
|
+
if key.name not in _services:
|
|
83
|
+
raise LookupError(f"required service not registered for key {key.name!r}")
|
|
84
|
+
return _services[key.name] # type: ignore[return-value]
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def reset_services() -> None:
|
|
88
|
+
"""Clear all registered services. Test-only."""
|
|
89
|
+
_services.clear()
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
# ---------------------------------------------------------------------------
|
|
93
|
+
# Hook registry, app context, and capability lifecycle primitives
|
|
94
|
+
# ---------------------------------------------------------------------------
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class HookRegistry:
|
|
98
|
+
"""Facade over OSS process-global hook setters; capabilities install through it."""
|
|
99
|
+
|
|
100
|
+
def set_tracer(self, tracer: Tracer | None) -> None:
|
|
101
|
+
"""Install (or clear) the process-global request tracer.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
tracer (Tracer | None): Tracer implementation, or None to disable.
|
|
105
|
+
"""
|
|
106
|
+
configure_tracer(tracer)
|
|
107
|
+
|
|
108
|
+
def set_usage_recorder(self, recorder: UsageEventRecorder | None) -> None:
|
|
109
|
+
"""Install (or clear) the process-global usage-event recorder.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
recorder (UsageEventRecorder | None): Recorder callable, or None to disable.
|
|
113
|
+
"""
|
|
114
|
+
configure_usage_event_recorder(recorder)
|
|
115
|
+
|
|
116
|
+
def set_retrieval_capture(self, hook: RetrievalCaptureHook | None) -> None:
|
|
117
|
+
"""Install (or clear) the process-global retrieval-capture hook.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
hook (RetrievalCaptureHook | None): Hook callable, or None to disable.
|
|
121
|
+
"""
|
|
122
|
+
configure_retrieval_capture_hook(hook)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@dataclass(frozen=True)
|
|
126
|
+
class AppContext:
|
|
127
|
+
"""Inter-step startup data passed to Capability.on_startup.
|
|
128
|
+
|
|
129
|
+
Mirrors the values that thread through reflexio_ext lifespan locals
|
|
130
|
+
(prepare_enterprise_startup output).
|
|
131
|
+
|
|
132
|
+
Attributes:
|
|
133
|
+
self_host_org_id (str | None): Org ID for self-hosted deployments, or None for platform.
|
|
134
|
+
activated (bool): Whether the enterprise activation check passed.
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
self_host_org_id: str | None = None
|
|
138
|
+
activated: bool = False
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class Capability(ABC):
|
|
142
|
+
"""An enterprise feature plugged into the app at its composition root.
|
|
143
|
+
|
|
144
|
+
All methods are no-op defaults so the registry can drive every capability
|
|
145
|
+
through the same phases without hasattr checks. The provider owns this
|
|
146
|
+
lifecycle shape (ABC); runtime-feature *contracts* are Protocols defined by OSS.
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
name: ClassVar[str]
|
|
150
|
+
|
|
151
|
+
def install_services(self) -> None: # noqa: B027
|
|
152
|
+
"""Register runtime-service providers (process-global). Default: none."""
|
|
153
|
+
|
|
154
|
+
def install_hooks(self, hooks: HookRegistry) -> None: # noqa: B027
|
|
155
|
+
"""Install pipeline hooks (tracer/usage-recorder/retrieval-capture). Default: none.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
hooks (HookRegistry): Registry to install hooks through.
|
|
159
|
+
"""
|
|
160
|
+
|
|
161
|
+
def routers(self, role: str) -> list[APIRouter]: # noqa: ARG002
|
|
162
|
+
"""Return routers to mount for this deployment role. Default: none.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
role (str): Deployment role (e.g. "all", "data-plane").
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
list[APIRouter]: Routers to mount; empty list by default.
|
|
169
|
+
"""
|
|
170
|
+
return []
|
|
171
|
+
|
|
172
|
+
async def on_startup(self, ctx: AppContext) -> None: # noqa: B027
|
|
173
|
+
"""Start daemons/schedulers. Raising aborts boot (fail-loud). Default: none.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
ctx (AppContext): Startup context produced by prepare_enterprise_startup.
|
|
177
|
+
"""
|
|
178
|
+
|
|
179
|
+
async def on_shutdown(self) -> None: # noqa: B027
|
|
180
|
+
"""Stop what on_startup started. Default: none."""
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
class CapabilityRegistry:
|
|
184
|
+
"""Holds additive capabilities plus the two singular construction-time concerns.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
capabilities (list[Capability]): Capability instances; names must be unique.
|
|
188
|
+
configurator_class (type | None): Single configurator class, or None.
|
|
189
|
+
billing_gate (Callable | None): Billing gate factory, or None.
|
|
190
|
+
role (str | None): Deployment role to thread into ``_wire_capabilities``
|
|
191
|
+
(e.g. ``"all"``, ``"data-plane"``). When ``None``, ``create_app``
|
|
192
|
+
derives the role from ``mount_data_plane``.
|
|
193
|
+
|
|
194
|
+
Raises:
|
|
195
|
+
RuntimeError: If two capabilities share the same name.
|
|
196
|
+
"""
|
|
197
|
+
|
|
198
|
+
def __init__(
|
|
199
|
+
self,
|
|
200
|
+
capabilities: list[Capability],
|
|
201
|
+
*,
|
|
202
|
+
configurator_class: type | None = None,
|
|
203
|
+
billing_gate: Callable[[str], Callable[..., None]] | None = None,
|
|
204
|
+
role: str | None = None,
|
|
205
|
+
) -> None:
|
|
206
|
+
names = [c.name for c in capabilities]
|
|
207
|
+
dupes = {n for n in names if names.count(n) > 1}
|
|
208
|
+
if dupes:
|
|
209
|
+
raise RuntimeError(f"duplicate capability names: {sorted(dupes)}")
|
|
210
|
+
self.capabilities = capabilities
|
|
211
|
+
self.configurator_class = configurator_class
|
|
212
|
+
self.billing_gate = billing_gate
|
|
213
|
+
self.role = role
|
|
@@ -195,12 +195,12 @@ _PROVIDER_DEFAULTS: dict[str, ProviderDefaults] = {
|
|
|
195
195
|
extraction_agent="gpt-5.5",
|
|
196
196
|
),
|
|
197
197
|
"anthropic": ProviderDefaults(
|
|
198
|
-
generation="claude-sonnet-
|
|
199
|
-
evaluation="claude-sonnet-
|
|
198
|
+
generation="claude-sonnet-5",
|
|
199
|
+
evaluation="claude-sonnet-5",
|
|
200
200
|
should_run="claude-haiku-4-5-20251001",
|
|
201
201
|
pre_retrieval="claude-haiku-4-5-20251001",
|
|
202
202
|
embedding=None,
|
|
203
|
-
extraction_agent="claude-sonnet-
|
|
203
|
+
extraction_agent="claude-sonnet-5",
|
|
204
204
|
),
|
|
205
205
|
"gemini": ProviderDefaults(
|
|
206
206
|
generation="gemini/gemini-3-flash-preview",
|
|
@@ -229,7 +229,7 @@ _PROVIDER_DEFAULTS: dict[str, ProviderDefaults] = {
|
|
|
229
229
|
should_run="minimax/MiniMax-M3",
|
|
230
230
|
pre_retrieval="minimax/MiniMax-M3",
|
|
231
231
|
embedding=None,
|
|
232
|
-
# Same
|
|
232
|
+
# Same M3 model handles resumable extraction. Surfaced by an
|
|
233
233
|
# e2e run on a MiniMax-only VPS where publish printed
|
|
234
234
|
# "No provider in ['minimax'] supports role=extraction_agent"
|
|
235
235
|
# warnings and silently skipped profile creation. Without this,
|
|
@@ -69,7 +69,7 @@ _CODEX_COMPAT_SCRIPT_NAMES = (
|
|
|
69
69
|
)
|
|
70
70
|
_CODEX_COMPAT_SCRIPT_NAME_SET = set(_CODEX_COMPAT_SCRIPT_NAMES)
|
|
71
71
|
_DEFAULT_TIMEOUT_SECONDS = 120
|
|
72
|
-
_DEFAULT_CLI_MODEL = "claude-sonnet-
|
|
72
|
+
_DEFAULT_CLI_MODEL = "claude-sonnet-5"
|
|
73
73
|
|
|
74
74
|
_TRUTHY_ENV_VALUES = {"1", "true", "yes"}
|
|
75
75
|
_UNSUPPORTED_PARAMS_WARNED: set[str] = set()
|
|
@@ -150,7 +150,18 @@ def score_pairs(query: str, docs: list[str]) -> list[float]:
|
|
|
150
150
|
return []
|
|
151
151
|
model = _get_model()
|
|
152
152
|
pairs = [(query, doc) for doc in docs]
|
|
153
|
-
|
|
153
|
+
try:
|
|
154
|
+
from torch import nn
|
|
155
|
+
except ImportError as e:
|
|
156
|
+
raise CrossEncoderUnavailableError(
|
|
157
|
+
"torch is not installed; cannot use the cross-encoder reranker"
|
|
158
|
+
) from e
|
|
159
|
+
|
|
160
|
+
raw_scores = model.predict(
|
|
161
|
+
pairs,
|
|
162
|
+
show_progress_bar=False,
|
|
163
|
+
activation_fn=nn.Identity(),
|
|
164
|
+
)
|
|
154
165
|
# ``predict`` returns a numpy array; convert to plain Python floats so
|
|
155
166
|
# the caller can serialise the result without numpy as a dependency.
|
|
156
167
|
return [float(s) for s in raw_scores]
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
active: true
|
|
3
3
|
description: "Context setting prompt for resumable playbook extraction. Every claim must be grounded in the conversation and generalized to a reusable task context; both requirements apply jointly to Correction SOPs and Success Path Recipes."
|
|
4
|
-
changelog: "v4.4.0: deliver the result as a native structured response — output the {{\"playbooks\": [...]}} JSON directly as the final assistant message instead of a finish_extraction tool call; ask_human/attach_pending_info_request remain real intermediate tools, and a plain (no-tool) final turn carrying the JSON is now the valid completion. No output schema change. v4.3.0: expands Correction SOP signals to include avoidable follow-ups, partial delivery, stalled conditional authorization, unverified claims, and incomplete coverage; requires Correction SOPs whenever correction signals are present; adds antecedent-not-reaction trigger guidance. (in-place 2026-06-18: make triggers future-user-query-like retrieval keys.) (in-place 2026-06-15: adds concise retrieve-general/action-specific guidance.) v4.2.3: strengthens strict tool-call discipline so plain-text/no-tool responses are explicitly invalid, including empty extraction outcomes. v4.2.2: replaces output examples with compact tool-call and JSON-shape guidance, while preserving the resumable extraction contract. v4.2.1: negative/avoid guidance cannot contradict the final verified implementation or final evaluation. v4.2.0: adds emergent skill-convention guidance for structuring multi-aspect playbook content as a small set of grouped do/avoid rules (no schema change). v4.1.7: adds resumable extraction guidance, names legacy fallback and malformed-input branches in boundary-change recipes, and avoids a polarity output field. (in-place 2026-05-30: tool-oriented finish_extraction output framing; deprecated and removed blocking_issue.) (in-place 2026-05-30: clarify in the Output Format section that finish_extraction may be preceded by ask_human/attach_pending_info_request; restructure examples into one no-tool example plus a worked ask_human and attach_pending_info_request example; stress that ask_human is rare and reserved for critical missing org-level facts — finish_extraction alone is the norm.) (in-place 2026-05-30: condense the resumable guidance — state finish-required/optional once, replace the duplicated Output Format block with a one-line pointer, and trim example prose.) (in-place 2026-06-03: consolidate and shorten the ask_human condition: ask only for missing shared context needed to know what to do, while still finishing extraction.) (in-place 2026-06-03: frame finish_extraction as the final completion tool and pending-info tools as intermediate.) (in-place 2026-06-03: clarify that empty finish_extraction alone must not replace ask_human when the ask_human condition applies.) (in-place 2026-06-04: clarify ask_human and attach_pending_info_request are alternatives for the same gap, not a sequence.)"
|
|
4
|
+
changelog: "v4.4.0: deliver the result as a native structured response — output the {{\"playbooks\": [...]}} JSON directly as the final assistant message instead of a finish_extraction tool call; ask_human/attach_pending_info_request remain real intermediate tools, and a plain (no-tool) final turn carrying the JSON is now the valid completion. No output schema change. (in-place 2026-06-28: strengthen the ask_human decision boundary for cases where a correction proves a durable shared procedure exists but only avoidance is grounded.) v4.3.0: expands Correction SOP signals to include avoidable follow-ups, partial delivery, stalled conditional authorization, unverified claims, and incomplete coverage; requires Correction SOPs whenever correction signals are present; adds antecedent-not-reaction trigger guidance. (in-place 2026-06-18: make triggers future-user-query-like retrieval keys.) (in-place 2026-06-15: adds concise retrieve-general/action-specific guidance.) v4.2.3: strengthens strict tool-call discipline so plain-text/no-tool responses are explicitly invalid, including empty extraction outcomes. v4.2.2: replaces output examples with compact tool-call and JSON-shape guidance, while preserving the resumable extraction contract. v4.2.1: negative/avoid guidance cannot contradict the final verified implementation or final evaluation. v4.2.0: adds emergent skill-convention guidance for structuring multi-aspect playbook content as a small set of grouped do/avoid rules (no schema change). v4.1.7: adds resumable extraction guidance, names legacy fallback and malformed-input branches in boundary-change recipes, and avoids a polarity output field. (in-place 2026-05-30: tool-oriented finish_extraction output framing; deprecated and removed blocking_issue.) (in-place 2026-05-30: clarify in the Output Format section that finish_extraction may be preceded by ask_human/attach_pending_info_request; restructure examples into one no-tool example plus a worked ask_human and attach_pending_info_request example; stress that ask_human is rare and reserved for critical missing org-level facts — finish_extraction alone is the norm.) (in-place 2026-05-30: condense the resumable guidance — state finish-required/optional once, replace the duplicated Output Format block with a one-line pointer, and trim example prose.) (in-place 2026-06-03: consolidate and shorten the ask_human condition: ask only for missing shared context needed to know what to do, while still finishing extraction.) (in-place 2026-06-03: frame finish_extraction as the final completion tool and pending-info tools as intermediate.) (in-place 2026-06-03: clarify that empty finish_extraction alone must not replace ask_human when the ask_human condition applies.) (in-place 2026-06-04: clarify ask_human and attach_pending_info_request are alternatives for the same gap, not a sequence.)"
|
|
5
5
|
variables:
|
|
6
6
|
- agent_context_prompt
|
|
7
7
|
- extraction_definition_prompt
|
|
@@ -26,6 +26,16 @@ Choose exactly one intermediate path per missing fact: use `attach_pending_info_
|
|
|
26
26
|
|
|
27
27
|
Do not ask for user-scoped/private facts, context derivable from the trajectory, agent context, tool list, or Prior Knowledge, complete avoidance-only rules, or merely nice-to-have details.
|
|
28
28
|
|
|
29
|
+
### Ask-human decision boundary
|
|
30
|
+
|
|
31
|
+
Before finalizing any Correction SOP from a negative correction, run this check:
|
|
32
|
+
|
|
33
|
+
1. Did the correction merely establish a complete avoidance rule, with no evidence that a shared positive procedure exists? If yes, emit the grounded avoidance rule and do not ask.
|
|
34
|
+
2. Did the correction state or strongly imply that a shared policy, process, approval path, escalation route, canonical target, standard wording, or other durable positive procedure exists, while leaving the actual target/procedure unknown? If yes, the future playbook is incomplete: call `ask_human` (or `attach_pending_info_request` if Prior Knowledge already has the same pending question).
|
|
35
|
+
3. Did the missing detail concern a single user, customer, client, assignment, record, or current factual lookup rather than a reusable agent/org rule? If yes, do not ask; emit only grounded guidance or no playbook.
|
|
36
|
+
|
|
37
|
+
Phrases like "specific process", "required workflow", "approval path", "special route", "canonical path/target", "standard policy", "standard wording/script", or "do not know the exact name/text" are strong evidence of a shared positive procedure gap when the conversation does not provide the missing procedure. Avoidance-only playbooks are acceptable interim guidance, but they are not a substitute for `ask_human` when the conversation proves that future agents need a missing shared positive action to handle the class of task correctly. The question should ask for the smallest durable missing fact, such as the approved route, required procedure, canonical target, policy threshold, or standard wording.
|
|
38
|
+
|
|
29
39
|
If an intermediate tool is needed, call exactly one of the two intermediate tools before delivering your final result. Then finalize the current run with any independently valid playbooks now, including avoidance rules; if none are valid without the answer, finalize with `{{"playbooks": []}}`.
|
|
30
40
|
|
|
31
41
|
When the `ask_human` condition applies, an `ask_human` tool call is required before finalization. Do not replace the question with an empty `{{"playbooks": []}}` final response alone.
|
|
@@ -161,6 +171,8 @@ Grounding constrains the *source* of a claim (it must come from the conversation
|
|
|
161
171
|
|
|
162
172
|
**When the agent doesn't know what to do:** Describe what to AVOID (the observed mistake) and state the limitation honestly. It is much better to say "do not fabricate order status — admit you cannot look it up" than to invent a specific alternative the agent may not actually have. If the missing "what to do" detail satisfies the `ask_human` condition above, use the tool; otherwise emit only the grounded avoidance rule or no playbook.
|
|
163
173
|
|
|
174
|
+
If a user correction says there is a specific process, approval path, route, standard, or policy but does not provide it, the extractor does not know enough to produce the durable positive SOP. Do not silently downgrade that case to avoidance-only unless avoidance is the complete reusable lesson; ask for the missing shared procedure and still finalize any grounded interim playbooks.
|
|
175
|
+
|
|
164
176
|
**Rule of thumb (both checks must pass):**
|
|
165
177
|
1. *Grounded* — if you remove the conversation and only read the `content`, could someone verify every claim by re-reading the conversation? If not, you've hallucinated.
|
|
166
178
|
2. *Generalized* — could a future agent in a similar task context, with no access to this conversation, apply the entry as written? If not, you've overfit — restate the pattern at the level of situation, artifact role, action sequence, verification, and detour.
|
|
@@ -175,7 +187,7 @@ For **Correction SOPs**:
|
|
|
175
187
|
3. Identify the violated implicit expectation
|
|
176
188
|
4. Draft the `trigger` (the problem or situation)
|
|
177
189
|
5. Tautology Check (see above)
|
|
178
|
-
6. Draft `content`: reason through what the agent did wrong and what the user's feedback tells us the agent should do differently. Ground every statement in evidence from the conversation. If the user told the agent what to do, capture that. If the user only told the agent what NOT to do, capture the avoidance. Do not guess what the right action is if the conversation doesn't tell you; use `ask_human` only when the condition above applies.
|
|
190
|
+
6. Draft `content`: reason through what the agent did wrong and what the user's feedback tells us the agent should do differently. Ground every statement in evidence from the conversation. If the user told the agent what to do, capture that. If the user only told the agent what NOT to do, capture the avoidance. If the user indicated that a shared process/policy/route/approval/standard/canonical target exists but did not provide it, call `ask_human` or attach to matching Prior Knowledge before finalizing; the avoidance rule alone does not answer what a future agent should do. Do not guess what the right action is if the conversation doesn't tell you; use `ask_human` only when the condition above applies.
|
|
179
191
|
|
|
180
192
|
For **Success Path Recipes**:
|
|
181
193
|
1. Identify whether the agent completed the task successfully
|