Skip to content

feat: Add tip provenance tracking and metadata - #73

Merged
visahak merged 4 commits into
AgentToolkit:mainfrom
visahak:feat/tip-provenance
Feb 24, 2026
Merged

feat: Add tip provenance tracking and metadata#73
visahak merged 4 commits into
AgentToolkit:mainfrom
visahak:feat/tip-provenance

Conversation

@visahak

@visahak visahak commented Feb 24, 2026

Copy link
Copy Markdown
Collaborator
  • Injects source_task_id and creation_mode to tip entities via save_trajectory and MCP create_entity.
  • Updates phoenix_sync to standardize source_task_id schema.
  • Adds comprehensive unit and E2E tests for metadata verification.
  • Updates main README and LOW_CODE_TRACING docs for tip provenance schema.

Summary by CodeRabbit

  • Documentation

    • Added "Tip Provenance" docs explaining provenance metadata (creation_mode, source_task_id/source_span_id) and examples.
  • New Features

    • Tips and guideline/policy entities now include provenance metadata indicating creation mode (auto-phoenix, auto-mcp, or manual) and source task/span identifiers.
  • Bug Fixes

    • Standardized an invalid-metadata error message to "Invalid JSON".
  • Tests

    • Added/updated tests to verify provenance metadata injection and expectations.

- Injects source_task_id and creation_mode to tip entities via save_trajectory and MCP create_entity.
- Updates phoenix_sync to standardize source_task_id schema.
- Adds comprehensive unit and E2E tests for metadata verification.
- Updates main README and LOW_CODE_TRACING docs for tip provenance schema.
@coderabbitai

coderabbitai Bot commented Feb 24, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds tip provenance metadata across code and docs: introduces creation_mode (auto-phoenix, auto-mcp, manual) and source_task_id/source_span_id fields for guideline/tip entities; updates MCP and Phoenix flows; and expands tests and documentation to validate and describe the metadata.

Changes

Cohort / File(s) Summary
Documentation
README.md, docs/LOW_CODE_TRACING.md
Added "Tip Provenance" guidance and a new section detailing provenance metadata fields and sample JSON (creation_mode, source_task_id, source_span_id, category).
MCP Server
kaizen/frontend/mcp/mcp_server.py
When saving trajectories, injects source_task_id and creation_mode: "auto-mcp" into guideline tips; create_entity now injects creation_mode: "manual" for guideline/policy types if absent and returns "Invalid JSON" on metadata parse failure.
Phoenix Sync
kaizen/sync/phoenix_sync.py
Renamed tip metadata key source_trace_idsource_task_id and injects creation_mode: "auto-phoenix" into tip metadata during trajectory processing.
End-to-end tests
tests/e2e/test_mcp.py
Added assertions that guideline entities persisted from save_trajectory contain source_task_id and creation_mode: "auto-mcp"; updated expected invalid-metadata error message.
Unit tests
tests/unit/test_mcp_server.py, tests/unit/test_phoenix_sync.py
Added unit tests covering metadata injection for auto/manual flows and ensuring non-applicable entity types do not receive creation_mode; updated phoenix sync tests to assert source_task_id, source_span_id, and creation_mode.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant MCP_Server as MCP Server
    participant Phoenix_Sync as Phoenix Sync
    participant Backend
    Client->>MCP_Server: save_trajectory(task_id)
    MCP_Server->>MCP_Server: generate_tips()
    MCP_Server->>MCP_Server: inject metadata\nsource_task_id=task_id\ncreation_mode="auto-mcp"
    MCP_Server->>Backend: update_entities(guidelines)
    Phoenix_Sync->>Phoenix_Sync: _process_trajectory(trace_id)
    Phoenix_Sync->>Phoenix_Sync: inject metadata\nsource_task_id=trace_id\nsource_span_id=span_id\ncreation_mode="auto-phoenix"
    Phoenix_Sync->>Backend: update_entities(tips)
    Client->>MCP_Server: create_entity(type="guideline", metadata?)
    MCP_Server->>MCP_Server: parse metadata\ninject creation_mode="manual" if missing
    MCP_Server->>Backend: update_entities(entity)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • vinodmut

Poem

🐰 I hopped through code, left tiny tracks—
Tips now wear ribbons of their past,
Phoenix, MCP, or a gentle hand—
Each provenance note tied to the task,
Hooray for stories that always last! 🌿

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: Add tip provenance tracking and metadata' accurately and concisely summarizes the main change—adding provenance metadata tracking to tips across multiple systems.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tests/e2e/test_mcp.py (1)

1-5: ⚠️ Potential issue | 🟠 Major

Ruff formatting check is failing CI — run ruff format before merging.

The GitHub Actions pipeline reports this file (among three) would be reformatted. As per coding guidelines, Ruff must be used for formatting (**/*.py).

uv run ruff format tests/e2e/test_mcp.py kaizen/frontend/mcp/mcp_server.py tests/unit/test_mcp_server.py
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/e2e/test_mcp.py` around lines 1 - 5, The file tests/e2e/test_mcp.py
fails the Ruff formatting check; run the formatter and commit the changes so CI
passes: run `ruff format` (or your editor integration) against
tests/e2e/test_mcp.py (and the other reported files) to normalize
imports/spacing and reformat per project rules, then stage the updated file(s)
and push; ensure the import block (e.g., the top-level imports like os, pytest,
json, Client, Path) is ordered and spaced according to Ruff so the GitHub
Actions pipeline no longer requests reformatting.
🧹 Nitpick comments (2)
tests/unit/test_mcp_server.py (1)

37-37: Prefer .kwargs over index [1] for call_args access.

call_args_list[1][1] and call_args[1] use the deprecated tuple-style access. The .kwargs attribute is more readable and won't silently break if mock internals change.

📝 Proposed fix (apply similarly at lines 59, 78, 97)
-        call_args = mock_get_client.update_entities.call_args_list[1][1]
+        call_args = mock_get_client.update_entities.call_args_list[1].kwargs
-    call_args = mock_get_client.update_entities.call_args[1]
+    call_args = mock_get_client.update_entities.call_args.kwargs
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/test_mcp_server.py` at line 37, Replace tuple-style access into
mock call args with the .kwargs attribute: locate usages of
mock_get_client.update_entities.call_args_list[1][1] (and the similar
occurrences at the other noted spots) in tests/unit/test_mcp_server.py and
change them to use mock_get_client.update_entities.call_args_list[1].kwargs so
the test reads the keyword arguments via .kwargs instead of indexing the
call_args tuple; update each occurrence referenced (including the ones at the
other commented lines) accordingly.
docs/LOW_CODE_TRACING.md (1)

219-237: source_span_id and category are undocumented in the bullet list.

The sample JSON at lines 219–230 exposes four fields, but only creation_mode and source_task_id have bullet-point explanations. source_span_id and category are left without descriptions, which will confuse readers.

📝 Suggested additions
 *   **`source_task_id`**: The originating trace ID (for Phoenix) or task ID (for MCP), linking the tip back to the specific execution that inspired it.
+*   **`source_span_id`**: The specific span ID within the trace (Phoenix only) that the tip was derived from.
+*   **`category`**: A descriptive label for the tip (e.g., `"optimization"`, `"strategy"`) assigned by the tip-generation model.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/LOW_CODE_TRACING.md` around lines 219 - 237, Add missing explanations
for the JSON fields "source_span_id" and "category" shown in the sample block:
describe that source_span_id links to the specific trace/span within Phoenix (a
finer-grained identifier than source_task_id) and that category classifies the
tip (e.g., "optimization", "bug", "style") to aid filtering, and append these
two bullet points to the existing list after the creation_mode and
source_task_id bullets so the four fields in the sample JSON are all documented.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@README.md`:
- Line 62: Update the README line describing `source_task_id` by replacing the
incorrect word "audibility" with "auditability" so the sentence reads that it
provides full auditability; locate the text containing the `source_task_id`
description and make that single-word correction.

In `@tests/e2e/test_mcp.py`:
- Around line 123-130: The test creates a new KaizenClient instance
(KaizenClient()) and never closes it, which leaks a Milvus connection; update
the mcp fixture to yield the existing kaizen_client (or otherwise expose it) so
tests reuse the fixture's kaizen_client instead of instantiating a new
KaizenClient, and modify the test to call the fixture-provided kaizen_client for
search_entities (or if you must create a new KaizenClient, ensure you call its
close/disconnect method in a finally block or use a context manager) so no extra
Milvus connection remains open.

In `@tests/unit/test_mcp_server.py`:
- Around line 15-102: Add the pytest unit marker to these tests by either
decorating each test function (test_save_trajectory_metadata_injection,
test_create_entity_metadata_injection_manual_guideline,
test_create_entity_metadata_injection_manual_policy,
test_create_entity_no_metadata_injection_for_other_types) with `@pytest.mark.unit`
(and ensure pytest is imported) or set a module-level marker via pytestmark =
pytest.mark.unit at top of the file; choose one approach and apply consistently
so pytest -m unit will pick up all four tests.

---

Outside diff comments:
In `@tests/e2e/test_mcp.py`:
- Around line 1-5: The file tests/e2e/test_mcp.py fails the Ruff formatting
check; run the formatter and commit the changes so CI passes: run `ruff format`
(or your editor integration) against tests/e2e/test_mcp.py (and the other
reported files) to normalize imports/spacing and reformat per project rules,
then stage the updated file(s) and push; ensure the import block (e.g., the
top-level imports like os, pytest, json, Client, Path) is ordered and spaced
according to Ruff so the GitHub Actions pipeline no longer requests
reformatting.

---

Nitpick comments:
In `@docs/LOW_CODE_TRACING.md`:
- Around line 219-237: Add missing explanations for the JSON fields
"source_span_id" and "category" shown in the sample block: describe that
source_span_id links to the specific trace/span within Phoenix (a finer-grained
identifier than source_task_id) and that category classifies the tip (e.g.,
"optimization", "bug", "style") to aid filtering, and append these two bullet
points to the existing list after the creation_mode and source_task_id bullets
so the four fields in the sample JSON are all documented.

In `@tests/unit/test_mcp_server.py`:
- Line 37: Replace tuple-style access into mock call args with the .kwargs
attribute: locate usages of mock_get_client.update_entities.call_args_list[1][1]
(and the similar occurrences at the other noted spots) in
tests/unit/test_mcp_server.py and change them to use
mock_get_client.update_entities.call_args_list[1].kwargs so the test reads the
keyword arguments via .kwargs instead of indexing the call_args tuple; update
each occurrence referenced (including the ones at the other commented lines)
accordingly.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9721a05 and 769d4fc.

📒 Files selected for processing (7)
  • README.md
  • docs/LOW_CODE_TRACING.md
  • kaizen/frontend/mcp/mcp_server.py
  • kaizen/sync/phoenix_sync.py
  • tests/e2e/test_mcp.py
  • tests/unit/test_mcp_server.py
  • tests/unit/test_phoenix_sync.py

Comment thread README.md

Kaizen automatically tracks the origin of every guideline it generates or stores. Every tip entity contains `metadata` identifying its source:
- `creation_mode`: Identifies how the tip was created (`auto-phoenix` via trace observability, `auto-mcp` via trajectory saving tools, or `manual`).
- `source_task_id`: The ID of the original trace or task that inspired the tip, providing full audibility.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Typo: "audibility" → "auditability"

audibility is not the intended word here.

📝 Proposed fix
-  `source_task_id`: The ID of the original trace or task that inspired the tip, providing full audibility.
+  `source_task_id`: The ID of the original trace or task that inspired the tip, providing full auditability.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- `source_task_id`: The ID of the original trace or task that inspired the tip, providing full audibility.
- `source_task_id`: The ID of the original trace or task that inspired the tip, providing full auditability.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` at line 62, Update the README line describing `source_task_id` by
replacing the incorrect word "audibility" with "auditability" so the sentence
reads that it provides full auditability; locate the text containing the
`source_task_id` description and make that single-word correction.

Comment thread tests/e2e/test_mcp.py
Comment on lines +123 to +130
from kaizen.frontend.client.kaizen_client import KaizenClient
from kaizen.config.kaizen import kaizen_config
client = KaizenClient()
entities = client.search_entities(
namespace_id=kaizen_config.namespace_id,
filters={"type": "guideline"},
limit=10,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

New KaizenClient instance is not explicitly closed.

The client created at line 125 is never closed within the test body. For the Milvus backend this adds a third connection to the same Milvus Lite instance (alongside the fixture's kaizen_client and the MCP server's _client), and relies entirely on the fixture teardown's blanket connections.disconnect / server_manager_instance.release_all() calls.

Consider yielding kaizen_client from the mcp fixture so the test can reuse the already-open connection instead of spawning a new one.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/e2e/test_mcp.py` around lines 123 - 130, The test creates a new
KaizenClient instance (KaizenClient()) and never closes it, which leaks a Milvus
connection; update the mcp fixture to yield the existing kaizen_client (or
otherwise expose it) so tests reuse the fixture's kaizen_client instead of
instantiating a new KaizenClient, and modify the test to call the
fixture-provided kaizen_client for search_entities (or if you must create a new
KaizenClient, ensure you call its close/disconnect method in a finally block or
use a context manager) so no extra Milvus connection remains open.

Comment on lines +15 to +102
def test_save_trajectory_metadata_injection(mock_get_client):
# Mock tip generation to prevent actual LLM calls
with patch("kaizen.frontend.mcp.mcp_server.generate_tips") as mock_generate_tips:
mock_result = MagicMock()
mock_tip = MagicMock()
mock_tip.content = "Always write unit tests"
mock_tip.category = "testing"
mock_tip.rationale = "Helps catch bugs early"
mock_tip.trigger = "writing code"
mock_result.tips = [mock_tip]
mock_result.task_description = "Add feature"
mock_generate_tips.return_value = mock_result

trajectory_data = json.dumps([{"role": "user", "content": "hi"}])
task_id = str(uuid.uuid4())

save_trajectory.fn(trajectory_data=trajectory_data, task_id=task_id)

# Ensure update_entities was called twice (once for trajectory, once for tips)
assert mock_get_client.update_entities.call_count == 2

# Second call is for tips
call_args = mock_get_client.update_entities.call_args_list[1][1]
entities = call_args["entities"]

assert len(entities) == 1
tip_entity = entities[0]
assert tip_entity.type == "guideline"
assert tip_entity.metadata["source_task_id"] == task_id
assert tip_entity.metadata["creation_mode"] == "auto-mcp"

def test_create_entity_metadata_injection_manual_guideline(mock_get_client):
mock_update = EntityUpdate(id="123", type="guideline", content="docstrings", event="ADD", metadata={"creation_mode": "manual"})
mock_get_client.update_entities.return_value = [mock_update]

# Missing explicit metadata, should auto-inject "manual"
result_str = create_entity.fn(
content="Write clear docstrings",
entity_type="guideline"
)
result = json.loads(result_str)
assert result["event"] == "ADD"
assert "id" in result

call_args = mock_get_client.update_entities.call_args[1]
entities = call_args["entities"]
assert len(entities) == 1
entity = entities[0]

assert entity.type == "guideline"
assert entity.metadata["creation_mode"] == "manual"

def test_create_entity_metadata_injection_manual_policy(mock_get_client):
mock_update = EntityUpdate(id="123", type="policy", content="PR reviews", event="ADD", metadata={"creation_mode": "manual"})
mock_get_client.update_entities.return_value = [mock_update]

result_str = create_entity.fn(
content="Require PR reviews",
entity_type="policy"
)
result = json.loads(result_str)
assert result["event"] == "ADD"

call_args = mock_get_client.update_entities.call_args[1]
entities = call_args["entities"]
entity = entities[0]

assert entity.type == "policy"
assert entity.metadata["creation_mode"] == "manual"

def test_create_entity_no_metadata_injection_for_other_types(mock_get_client):
mock_update = EntityUpdate(id="123", type="log", content="App started", event="ADD", metadata={})
mock_get_client.update_entities.return_value = [mock_update]

# A generic log entity shouldn't get creation_mode injected
result_str = create_entity.fn(
content="App started",
entity_type="log"
)
result = json.loads(result_str)
assert result["event"] == "ADD"

call_args = mock_get_client.update_entities.call_args[1]
entities = call_args["entities"]
entity = entities[0]

assert entity.type == "log"
assert "creation_mode" not in (entity.metadata or {})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Missing @pytest.mark.unit on all test functions.

All four tests are pure unit tests (fully mocked, no I/O) but none carry the @pytest.mark.unit marker. This prevents targeted test runs (e.g., pytest -m unit) from including them. As per coding guidelines, unit tests in tests/**/*.py must use the unit pytest marker.

📝 Proposed fix
+@pytest.mark.unit
 def test_save_trajectory_metadata_injection(mock_get_client):

+@pytest.mark.unit
 def test_create_entity_metadata_injection_manual_guideline(mock_get_client):

+@pytest.mark.unit
 def test_create_entity_metadata_injection_manual_policy(mock_get_client):

+@pytest.mark.unit
 def test_create_entity_no_metadata_injection_for_other_types(mock_get_client):

Alternatively, add a module-level pytestmark:

+pytestmark = pytest.mark.unit
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/test_mcp_server.py` around lines 15 - 102, Add the pytest unit
marker to these tests by either decorating each test function
(test_save_trajectory_metadata_injection,
test_create_entity_metadata_injection_manual_guideline,
test_create_entity_metadata_injection_manual_policy,
test_create_entity_no_metadata_injection_for_other_types) with `@pytest.mark.unit`
(and ensure pytest is imported) or set a module-level marker via pytestmark =
pytest.mark.unit at top of the file; choose one approach and apply consistently
so pytest -m unit will pick up all four tests.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
tests/unit/test_mcp_server.py (1)

17-98: ⚠️ Potential issue | 🟡 Minor

Add the unit pytest marker for these tests.

These are unit tests but lack the required marker, which breaks pytest -m unit filtering. Consider a module-level marker for consistency.

✅ Proposed fix (module-level marker)
 import json
 import uuid
 import pytest
 from unittest.mock import patch, MagicMock
 
 from kaizen.frontend.mcp.mcp_server import save_trajectory, create_entity
 from kaizen.schema.conflict_resolution import EntityUpdate
+
+pytestmark = pytest.mark.unit

As per coding guidelines "tests/**/*.py: Use pytest markers e2e for end-to-end tests and unit for unit tests in test files".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/test_mcp_server.py` around lines 17 - 98, These tests are missing
the pytest "unit" marker; add a module-level marker by importing pytest at the
top and setting pytestmark = pytest.mark.unit so all functions
(test_save_trajectory_metadata_injection,
test_create_entity_metadata_injection_manual_guideline,
test_create_entity_metadata_injection_manual_policy,
test_create_entity_no_metadata_injection_for_other_types) are marked as unit
tests for pytest -m unit filtering.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@tests/unit/test_mcp_server.py`:
- Around line 17-98: These tests are missing the pytest "unit" marker; add a
module-level marker by importing pytest at the top and setting pytestmark =
pytest.mark.unit so all functions (test_save_trajectory_metadata_injection,
test_create_entity_metadata_injection_manual_guideline,
test_create_entity_metadata_injection_manual_policy,
test_create_entity_no_metadata_injection_for_other_types) are marked as unit
tests for pytest -m unit filtering.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 769d4fc and 1653652.

📒 Files selected for processing (3)
  • kaizen/frontend/mcp/mcp_server.py
  • tests/e2e/test_mcp.py
  • tests/unit/test_mcp_server.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/e2e/test_mcp.py
  • kaizen/frontend/mcp/mcp_server.py

@visahak
visahak merged commit 1154b4a into AgentToolkit:main Feb 24, 2026
15 checks passed
@visahak
visahak deleted the feat/tip-provenance branch February 24, 2026 17:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants