Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
e0b0fbc
feat: add open-world evidence foundation (#407)
guangyu-reflexio Aug 3, 2026
bfff403
fix: make receipt finalization winner-aware (#408)
guangyu-reflexio Aug 3, 2026
6ffeb1c
fix: preserve offline tuner exposure evidence (#409)
guangyu-reflexio Aug 3, 2026
b8795bf
fix: fingerprint internal user playbook metadata
guangyu-reflexio Aug 4, 2026
18683a0
fix: retry receipt-backed learning billing
guangyu-reflexio Aug 4, 2026
58e0a4d
fix: keep receipt billing obligations retryable
guangyu-reflexio Aug 4, 2026
e1d7ca0
fix: pin receipt billing event time
guangyu-reflexio Aug 4, 2026
56287b5
fix: use loaded run time for receipt billing
guangyu-reflexio Aug 4, 2026
70bc28e
fix: require claims for governance rebuild calls
guangyu-reflexio Aug 4, 2026
0d3dd5d
fix: fence sqlite governance rebuild mutations
guangyu-reflexio Aug 4, 2026
46a218a
fix: preserve receipt billing delivery outcomes
guangyu-reflexio Aug 4, 2026
ea777b0
fix: add always-evaluated global scheduler hooks
guangyu-reflexio Aug 4, 2026
dfca8aa
fix: bound search exposures and release sqlite locks
guangyu-reflexio Aug 4, 2026
50480d8
fix: resolve shared rebuild integration
guangyu-reflexio Aug 5, 2026
b698092
fix: run always-global sweeps before bootstrap
guangyu-reflexio Aug 5, 2026
592b30a
fix: bound session outcome migration queries
guangyu-reflexio Aug 5, 2026
4219500
fix: restore OSS E2E billing exemption
guangyu-reflexio Aug 5, 2026
e85fa24
test: cover session outcome migration rollback
guangyu-reflexio Aug 5, 2026
38eb8d3
test: isolate generation billing from E2E mode
guangyu-reflexio Aug 5, 2026
a4dd97d
test: isolate playbook retry from E2E mode
guangyu-reflexio Aug 5, 2026
fb89837
fix: bound session outcome migration memory
guangyu-reflexio Aug 5, 2026
ac40f9d
fix: harden session outcome identity contracts
guangyu-reflexio Aug 5, 2026
0b7578b
fix: restore fail-open usage lifecycle
guangyu-reflexio Aug 5, 2026
f6d8c0c
docs: clarify receipt billing retry ownership
guangyu-reflexio Aug 5, 2026
af287fa
fix(storage): align trajectory depth boundary
guangyu-reflexio Aug 5, 2026
343414f
fix(search): record direct playbook exposures
guangyu-reflexio Aug 5, 2026
e92bdca
fix(search): cap direct playbook result batches
guangyu-reflexio Aug 5, 2026
064bbc6
docs: scope search exposure guarantee
guangyu-reflexio Aug 5, 2026
cf20c2a
fix(outcomes): constrain source labels
guangyu-reflexio Aug 5, 2026
923534c
fix(outcomes): apply source contract to filters
guangyu-reflexio Aug 5, 2026
1c3c144
docs: define source label contract
guangyu-reflexio Aug 5, 2026
d84e1b7
fix: serialize purge heartbeat renewals
guangyu-reflexio Aug 6, 2026
64b5ac2
test: make heartbeat renewal regression deterministic
guangyu-reflexio Aug 6, 2026
1435c63
test: prove heartbeat renewal overlap deterministically
guangyu-reflexio Aug 6, 2026
67a02da
test: narrow heartbeat renewal exception capture
guangyu-reflexio Aug 6, 2026
963c284
test: cover unregistered session outcome acceptance
guangyu-reflexio Aug 6, 2026
2dcd5f5
Validate direct search identifier lengths
guangyu-reflexio Aug 6, 2026
7e024e3
test: harden concurrent test assertions
guangyu-reflexio Aug 7, 2026
6b6e6c2
feat: add open-world optimizer identity
guangyu-reflexio Aug 7, 2026
c970863
fix: upgrade generic evidence artifact storage
guangyu-reflexio Aug 8, 2026
063be9f
fix: harden SQLite optimization storage invariants
guangyu-reflexio Aug 9, 2026
fe4bfda
test: align exposure boundary with queued metering
guangyu-reflexio Aug 11, 2026
3d4186b
fix: align Phase 1 tests with current runtime
guangyu-reflexio Aug 11, 2026
7e42b17
test: align lifecycle coverage with queued metering
guangyu-reflexio Aug 10, 2026
0d2f77d
fix: clean up partial metering worker starts
guangyu-reflexio Aug 10, 2026
00b8bfd
fix: preserve finalized session outcomes
guangyu-reflexio Aug 10, 2026
3577a6f
fix: preserve legacy request sources on read
guangyu-reflexio Aug 10, 2026
6a58401
fix: stream session outcome trajectory digests
guangyu-reflexio Aug 10, 2026
81447e0
fix: make exposure retention policy fixed
guangyu-reflexio Aug 10, 2026
d3e88f5
fix: bound shared maintenance lifecycle
guangyu-reflexio Aug 10, 2026
09b7c60
fix: stream canonical trajectory sizing
guangyu-reflexio Aug 11, 2026
5c02048
fix: allow synchronous playbook aggregation to finish
guangyu-reflexio Aug 11, 2026
531b18c
fix: address Phase 2 storage review findings
guangyu-reflexio Aug 11, 2026
9c54640
test: cover streamed migration trajectories
guangyu-reflexio Aug 11, 2026
0892a97
fix: normalize nullable source on outcome retries
guangyu-reflexio Aug 11, 2026
823d0a4
test: harden session outcome storage guards
guangyu-reflexio Aug 11, 2026
056fc97
fix: wait for local embedding before backend startup
guangyu-reflexio Aug 12, 2026
6eab375
fix: persist inline finalization receipts
guangyu-reflexio Aug 13, 2026
25ac8dd
fix: keep tuner evidence production-only
guangyu-reflexio Aug 15, 2026
e2e7334
fix: expose playbook search correlation ids
guangyu-reflexio Aug 15, 2026
19864ac
test: mark readiness match as regex
guangyu-reflexio Aug 15, 2026
f614717
docs: make SQLite check work without uv
guangyu-reflexio Aug 15, 2026
3a24629
test: model missing finalization receipts
guangyu-reflexio Aug 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions AI_AGENT_INTEGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ learns, what stays private to a user scope, and what transfers to other users.
| `user_id` | Scope for profiles and user playbooks | Use the human user, tenant, workspace, repo, or project whose preferences should be isolated. For example, use a project id when repo-specific rules should not leak into unrelated repos. |
| `agent_version` | Scope for shared agent playbooks | Use a stable agent name plus major behavior version, for example `my-agent-v1`. Keep it stable if learnings should transfer across users/projects. If you omit it, the SDK uses `DEFAULT_AGENT_VERSION` (`"agent-v0"`) — fine for a single agent, but set an explicit value before you run more than one. |
| `session_id` | Group turns for one conversation | Use the host session/conversation id. Generate a UUID if the host does not provide one. |
| `source` | Audit label | Use the integration name, for example `my-agent-plugin`. |
| `source` | Producer/workflow label | Use a non-sensitive machine label such as `support-agent:v2`. Where the API permits an empty value, empty means the source is absent. Every non-empty value must match `^[a-z0-9][a-z0-9._:-]{0,127}$` and is limited to 128 ASCII characters. Do not include user identifiers, email addresses, or other PII. |

`user_id` and `agent_version` work together:

Expand Down Expand Up @@ -234,13 +234,17 @@ Publish request fields:
| --- | --- | --- | --- |
| `user_id` | Yes | The user, tenant, workspace, repo, or project scope whose private profiles and user playbooks should be isolated. | `"alice"`, `"tenant-acme"`, `"repo-reflexio"` |
| `interactions` / `interaction_data_list` | Yes | Ordered conversation turns to publish. Include at least one turn; multi-turn correction examples are best for learning. | `[{"role": "User", "content": "Use pnpm here."}, {"role": "Assistant", "content": "Got it, I will use pnpm."}]` |
| `source` | No | Integration label for debugging and filtering. Use a stable name for the plugin, framework, or adapter. | `"my-agent-plugin"`, `"vscode-assistant"`, `"support-chatbot"` |
| `source` | No | Non-sensitive producer/workflow machine label for debugging and filtering. Empty means the source is absent where the API permits it. Every non-empty value must match `^[a-z0-9][a-z0-9._:-]{0,127}$` and is limited to 128 ASCII characters. Do not include user identifiers, email addresses, or other PII. | `"support-agent:v2"` |
| `agent_version` | Strongly recommended | The shared-agent learning boundary. Use the same value when playbooks should transfer across users. Change it when old playbooks should not transfer. | `"support-agent-v1"`, `"coding-agent-2026-05"` |
| `session_id` | Yes | Host conversation/session id. Generate a UUID if the host has no session id, and reuse it for all turns in that conversation. | `"sess_01HX8Y..."`, `"3f02b7f8-..."` |
| `skip_aggregation` | No | `False` when user playbooks should be eligible to roll up into shared agent playbooks. `True` when you want user-level extraction only. | `false` |
| `force_extraction` | No | `False` for normal background publishing. `True` for manual learn-now, tests, or final flushes where you intentionally want extraction to run immediately. | `false` |
| `wait_for_response` | SDK/query option | `False` on interactive paths. `True` only when the caller is prepared to wait for extraction results. | `false` |

The source contract applies to new publish and filter inputs. Read responses may
return historical source values created before this contract; Reflexio preserves
those values exactly because source participates in session-outcome identity.

Each interaction row should resemble Reflexio's `InteractionData` shape:

```json
Expand Down Expand Up @@ -315,7 +319,7 @@ def publish_turns(
response = client.publish_interaction(
user_id=user_id,
interactions=interactions,
source="my-agent-plugin",
source="support-agent:v2",
agent_version=agent_version,
session_id=session_id,
wait_for_response=False,
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,15 @@ Publish conversations from your agent, and Reflexio closes the self-improvement
| --- | --- |
| [Python](https://www.python.org/) >= 3.12 | Required for PyPI and source installs |
| [uv](https://docs.astral.sh/uv/getting-started/installation/) | Required when running from source |
| Python's linked SQLite runtime | >= 3.35.0 (required for local SQLite storage; not the standalone `sqlite3` CLI) |
| [Node.js](https://nodejs.org/) >= 18 | Required only for the local docs site in a source checkout |

Check the SQLite runtime linked to Python with:

```shell
python -c "import sqlite3; print(sqlite3.sqlite_version_info)"
```
Comment thread
guangyu-reflexio marked this conversation as resolved.

<p align="center">
<img src="docs/images/demo.gif" width="800px" alt="Reflexio Demo">
</p>
Expand Down
10 changes: 5 additions & 5 deletions docs/lib/methods/requests-sessions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export const requestSessionMethods: MethodDef[] = [
displayName: "Mark Session Outcome",
group: "requests-sessions",
description:
"Record the lifetime-durable first success or failure marker for an existing session. User and source are derived from the first request.",
"Record the lifetime-durable first terminal outcome for an existing session. Idempotency requires unchanged payload, governance context, and finalized trajectory/session context; changed values conflict. User and source are derived from the first request.",
httpMethod: "POST",
endpoint: "/api/session_outcome",
requestStyle: "json_body",
Expand All @@ -17,8 +17,8 @@ export const requestSessionMethods: MethodDef[] = [
name: "outcome",
type: "enum",
required: true,
description: "Terminal outcome",
enumValues: ["success", "failure"],
description: "Terminal outcome: success, failure, or unknown",
enumValues: ["success", "failure", "unknown"],
},
{ name: "occurred_at", type: "number", required: true, description: "Unix epoch seconds when the outcome occurred" },
{ name: "label", type: "string", required: false, description: "Optional label, at most 128 characters" },
Expand All @@ -43,8 +43,8 @@ export const requestSessionMethods: MethodDef[] = [
name: "outcome",
type: "enum",
required: false,
description: "Exact outcome filter",
enumValues: ["success", "failure"],
description: "Exact outcome filter: success, failure, or unknown",
enumValues: ["success", "failure", "unknown"],
},
{ name: "label", type: "string", required: false, description: "Exact label filter" },
{ name: "start_time", type: "number", required: false, description: "Inclusive minimum event time" },
Expand Down
21 changes: 18 additions & 3 deletions docs/lib/methods/unified-search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const unifiedSearchMethods: MethodDef[] = [
type: "number",
required: false,
default: 5,
description: "Maximum results per entity type",
description: "Maximum results per entity type, from 1 to 100",
},
{
name: "threshold",
Expand All @@ -50,7 +50,8 @@ export const unifiedSearchMethods: MethodDef[] = [
name: "user_id",
type: "string",
required: false,
description: "Filter by user ID (profiles, user_playbooks)",
description:
"Filter by user ID (profiles, user_playbooks), at most 255 characters",
},
{
name: "entity_types",
Expand Down Expand Up @@ -90,12 +91,26 @@ export const unifiedSearchMethods: MethodDef[] = [
"Search mode: vector (embedding similarity), fts (full-text search), or hybrid (combined with RRF)",
enumValues: ["vector", "fts", "hybrid"],
},
{
name: "request_id",
type: "string",
required: false,
description:
"Caller correlation ID for the search turn, at most 255 characters",
},
{
name: "session_id",
type: "string",
required: false,
description:
"Agent session this search serves. When set, results already returned to the same session are skipped and next-best matches backfilled; searches without it neither read nor record session dedup state",
"Agent session this search serves, at most 255 characters. When set, results already returned to the same session are skipped and next-best matches backfilled; searches without it neither read nor record session dedup state",
},
{
name: "interaction_id",
type: "number",
required: false,
description:
"Caller interaction ID for the search turn; must be a positive integer (minimum 1)",
},
],
},
Expand Down
14 changes: 13 additions & 1 deletion docs/lib/methods/user-playbooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,18 @@ export const userPlaybookMethods: MethodDef[] = [
required: false,
description: "Filter by user (via request_id linkage to requests table)",
},
{
name: "request_id",
type: "string",
required: false,
description: "Caller correlation ID, at most 255 characters",
},
{
name: "session_id",
type: "string",
required: false,
description: "Caller session ID, at most 255 characters",
},
{
name: "agent_version",
type: "string",
Expand Down Expand Up @@ -105,7 +117,7 @@ export const userPlaybookMethods: MethodDef[] = [
type: "number",
required: false,
default: 10,
description: "Maximum number of results to return",
description: "Maximum number of results to return, from 1 to 100",
},
{
name: "threshold",
Expand Down
2 changes: 1 addition & 1 deletion reflexio/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ Apply to all three modes:
| `--wait` | Block until server-side extraction finishes (returns real counts). |
| `--session-id` | Required unless each payload includes `session_id`; group publishes into one session. |
| `--agent-version` | Tag the interaction with an agent version (used by playbook filtering). |
| `--source` | Free-form source tag (defaults to `cli`). |
| `--source` | Non-sensitive producer/workflow label matching `^[a-z0-9][a-z0-9._:-]{0,127}$`; never use user identifiers or PII (defaults to `cli`). |
| `--skip-aggregation`| Extract profiles/playbooks but skip playbook aggregation. |
| `--force-extraction`| Bypass all extraction gates (`stride_size`, cheap pre-filter, LLM `should_run`) and always run extractors. |
| `--evaluation-only` | Store the request for session-level evaluation only; requires `--session-id` and skips profile/playbook extraction. |
Expand Down
16 changes: 16 additions & 0 deletions reflexio/cli/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -504,8 +504,24 @@ def run_services(
for path in supervisor.stop_request_paths.values():
remove_pidfile(path)

service_names = {svc.name for svc in services}
gate_local_embedding = {"embedding", "backend"}.issubset(service_names)

try:
if gate_local_embedding:
embedding = next(svc for svc in services if svc.name == "embedding")
supervisor.start_service(embedding)
if not _wait_for_all_ready(
{"embedding": supervisor.ready_events["embedding"]},
{"embedding": supervisor.processes["embedding"]},
):
raise RuntimeError(
"embedding service did not become ready before backend startup"
)

for svc in services:
if gate_local_embedding and svc.name == "embedding":
continue
supervisor.start_service(svc)
supervisor.write_current_pidfile()
except (OSError, RuntimeError):
Expand Down
44 changes: 33 additions & 11 deletions reflexio/client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -528,7 +528,9 @@ def publish_interaction(
Args:
user_id: The user ID.
interactions: List of interaction data.
source: The source of the interaction.
source: Non-sensitive producer/workflow label. A non-empty value
must match ``^[a-z0-9][a-z0-9._:-]{0,127}$`` and must not
contain user identifiers or PII.
agent_version: The agent version.
session_id: Required non-empty session ID for grouping requests.
wait_for_response: If True, the **server** waits for
Expand Down Expand Up @@ -893,6 +895,8 @@ def search_user_playbooks(
threshold: float | None = None,
enable_reformulation: bool | None = None,
search_mode: SearchMode | None = None,
request_id: str | None = None,
session_id: str | None = None,
) -> SearchUserPlaybooksViewResponse:
"""Search for user playbooks with semantic/text search and filtering.

Expand All @@ -906,10 +910,14 @@ def search_user_playbooks(
end_time (Optional[datetime]): End time for created_at filter
status_filter (Optional[list[Optional[Status]]]): Filter by status (None for CURRENT, PENDING, ARCHIVED)
tags (Optional[list[str]]): Match playbooks having any of these tags.
top_k (Optional[int]): Maximum number of results to return (default: 10)
top_k (Optional[int]): Maximum results to return, from 1 to 100 (default: 10)
Comment thread
guangyu-reflexio marked this conversation as resolved.
threshold (Optional[float]): Similarity threshold for vector search.
When omitted, the embedding model's default is used.
enable_reformulation (Optional[bool]): Enable LLM query reformulation (default: False)
request_id (Optional[str]): Caller correlation ID for the search turn,
at most 255 characters.
session_id (Optional[str]): Caller session ID for the search turn,
at most 255 characters.

Returns:
SearchUserPlaybooksViewResponse: Response containing matching user playbooks
Expand All @@ -929,6 +937,8 @@ def search_user_playbooks(
threshold=threshold,
enable_reformulation=enable_reformulation,
search_mode=search_mode,
request_id=request_id,
session_id=session_id,
)
response = self._make_request(
"POST", "/api/search_user_playbooks", json=req.model_dump(mode="json")
Expand Down Expand Up @@ -1210,13 +1220,20 @@ def mark_session_outcome(
value: float | None = None,
metadata: dict[str, Any] | None = None,
) -> SetSessionOutcomeResponse:
"""Record the first terminal outcome for a published session.
"""Record the immutable first outcome for a published session.

The session must already contain at least one published request. Reflexio
derives both ``user_id`` and ``source`` from the earliest request ordered
by ``(created_at, request_id)``. Only the first outcome is recorded;
retries return ``success=True`` and ``recorded=False``. Sessions are not
required to report an outcome.
by ``(created_at, request_id)``. New canonical rows bind the outcome to
the server-owned outcome contract and canonical finalized trajectory. An
exact canonical retry must match the payload, contract, and trajectory;
otherwise it is rejected with ``reason="conflicting_finalization"``.
Rolling-upgrade rows with all four identity fields null compare the
caller payload and any available server-derived session context, but
cannot compare absent contract or trajectory digests. An accepted retry
preserves all four null identity fields and returns ``success=True`` and
``recorded=False``. Sessions may report ``success``, ``failure``, or
``unknown`` and are not required to report an outcome.
"""
request = SetSessionOutcomeRequest(
session_id=session_id,
Expand Down Expand Up @@ -2822,12 +2839,14 @@ def search(
Args:
request (Optional[UnifiedSearchRequest]): The search request object (alternative to kwargs)
query (str): Search query text
top_k (Optional[int]): Maximum results per entity type (default: 5)
top_k (Optional[int]): Maximum results per entity type, from 1 to 100
(default: 5).
threshold (Optional[float]): Similarity threshold for vector search.
When omitted, the embedding model's default is used.
agent_version (Optional[str]): Filter by agent version (agent_playbooks, user_playbooks)
playbook_name (Optional[str]): Filter by playbook name (agent_playbooks, user_playbooks)
user_id (Optional[str]): Filter by user ID (profiles, user_playbooks)
user_id (Optional[str]): Filter by user ID (profiles, user_playbooks),
at most 255 characters.
tags (Optional[list[str]]): Match entities having any requested tag.
entity_types (Optional[list[str]]): Entity types to search. Valid values:
"profiles", "user_playbooks", "agent_playbooks".
Expand All @@ -2843,9 +2862,12 @@ def search(
the configured search backend supports it (default: False).
conversation_history (Optional[list[ConversationTurn] | list[dict]]): Prior conversation turns for context-aware query reformulation. Accepts ConversationTurn objects or dicts with "role" and "content" keys.
search_mode (Optional[SearchMode | str]): Search mode to use. Accepts SearchMode enum or string value ("vector", "fts", "hybrid").
request_id (Optional[str]): Caller correlation id for the search turn.
session_id (Optional[str]): Caller session id for the search turn.
interaction_id (Optional[int]): Caller interaction id for the search turn.
request_id (Optional[str]): Caller correlation ID for the search turn,
at most 255 characters.
session_id (Optional[str]): Caller session ID for the search turn,
at most 255 characters. Also enables session-scoped result deduplication.
interaction_id (Optional[int]): Caller interaction ID for the search
turn; must be a positive integer (minimum 1).

Returns:
UnifiedSearchViewResponse: Combined search results from all entity types
Expand Down
Loading
Loading