-
Notifications
You must be signed in to change notification settings - Fork 332
feat: add Claude Code skills Memanto memory bridge #613
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
attaboy11
wants to merge
8
commits into
moorcheh-ai:main
from
attaboy11:attaboy11/claudecode-skills-memanto-bridge
Closed
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
fd8db38
Add Claude Code skills Memanto bridge demo
attaboy11 f17789d
Address skills bridge review feedback
attaboy11 47f1d45
Document skills memory bridge example
attaboy11 752f3bf
Harden skills demo file memory backend
attaboy11 1acabd9
Add skills bridge tests
attaboy11 08b23f6
Make skills memory bridge reusable
attaboy11 dfece30
Harden skills bridge memory tags
attaboy11 652e68f
Sanitize skills memory tags
attaboy11 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,150 @@ | ||
| # Claude Code Skills + Memanto Memory Bridge | ||
|
|
||
| This example shows how Memanto can act as a global memory companion across separate developer skill executions. | ||
|
|
||
| The bridge has two lifecycle hooks: | ||
|
|
||
| - `before_skill(...)`: recall relevant engineering memory and return a concise context block that can be appended to a skill prompt. | ||
| - `after_skill(...)`: extract durable project decisions, coding preferences, and codebase quirks from a completed skill transcript, then store them in Memanto. | ||
| - `run_with_memory(...)`: wrap any existing skill runner callable with both hooks, so teams can drop the bridge around their current `/tdd`, `/handoff`, or custom skill executor without rewriting those skills. | ||
|
|
||
|  | ||
|
|
||
| ## Why This Matters | ||
|
|
||
| Developer skills are intentionally small and focused. That is useful, but it means a decision captured during a review skill can disappear before a later testing or implementation skill starts. | ||
|
|
||
| This example keeps those decisions outside the individual skill run: | ||
|
|
||
| 1. `/grill-with-docs` reviews an architecture plan and records durable decisions. | ||
| 2. A fresh `/tdd` run asks for tests in the same project. | ||
| 3. Memanto recalls the earlier decisions and injects them as a compact engineering-memory block. | ||
|
|
||
| ## Files | ||
|
|
||
| ```text | ||
| examples/claudecode-skills-memanto/ | ||
| |-- README.md | ||
| |-- assets/demo.gif | ||
| |-- make_demo_gif.py | ||
| |-- memory_backends.py | ||
| |-- requirements.txt | ||
| |-- run_cross_skill_demo.py | ||
| |-- skill_memory_bridge.py | ||
| `-- tests/test_skill_memory_bridge.py | ||
| ``` | ||
|
|
||
| ## Quick Start | ||
|
|
||
| ```bash | ||
| cd examples/claudecode-skills-memanto | ||
| python -m venv .venv | ||
| source .venv/bin/activate | ||
| pip install -r requirements.txt | ||
|
|
||
| # Runs without external keys by using the local JSON backend. | ||
| python run_cross_skill_demo.py --backend file --reset | ||
| ``` | ||
|
|
||
| ## Run With Memanto | ||
|
|
||
| Install and configure Memanto first: | ||
|
|
||
| ```bash | ||
| pip install memanto | ||
| memanto | ||
| ``` | ||
|
|
||
| Then run the same bridge against the real Memanto CLI backend: | ||
|
|
||
| ```bash | ||
| python run_cross_skill_demo.py --backend memanto --agent-id claudecode-skills-demo | ||
| ``` | ||
|
|
||
| ## Integration Pattern | ||
|
|
||
| Wrap skill execution with the bridge directly: | ||
|
|
||
| ```python | ||
| from skill_memory_bridge import SkillMemoryBridge, SkillRun | ||
|
|
||
| bridge = SkillMemoryBridge(memory_backend) | ||
| run = SkillRun( | ||
| skill_name="/tdd", | ||
| task="Add tests for invoice webhook idempotency", | ||
| file_paths=["apps/billing/webhooks/stripe.ts"], | ||
| ) | ||
|
|
||
| memory_context = bridge.before_skill(run) | ||
|
|
||
| skill_prompt = f"{memory_context}\n\n{original_skill_prompt}" | ||
|
|
||
| result = run_skill(skill_prompt) | ||
|
|
||
| bridge.after_skill(run, result.transcript) | ||
| ``` | ||
|
|
||
| Or use the executor-agnostic wrapper when you already have a function that runs a skill: | ||
|
|
||
| ```python | ||
| def run_skill(prompt: str) -> str: | ||
| # Call your existing Claude Code, shell, or local skill runner here. | ||
| return "Learning: Invoice export tests should preserve customer locale." | ||
|
|
||
| result = bridge.run_with_memory( | ||
| run, | ||
| "Create a handoff note for the invoice export branch.", | ||
| run_skill, | ||
| ) | ||
|
|
||
| print(result.prompt) | ||
| print(result.stored_memories) | ||
| ``` | ||
|
|
||
| `SkillRun.metadata` can carry project, framework, branch, tenant, or issue identifiers into the recall query without changing the bridge internals: | ||
|
|
||
| ```python | ||
| run = SkillRun( | ||
| skill_name="/tdd", | ||
| task="Add route tests", | ||
| file_paths=["apps/mobile/app/(tabs)/index.tsx"], | ||
| metadata={"framework": "expo-router", "project": "mobile-app"}, | ||
| ) | ||
| ``` | ||
|
|
||
| The bridge deliberately stores only durable engineering facts: | ||
|
|
||
| - `Decision: Keep Stripe webhook handlers idempotent by event id.` | ||
| - `Preference: Tests should cover replayed webhook payloads.` | ||
| - `Quirk: Billing code stores timestamps as UTC ISO strings.` | ||
|
|
||
| It avoids saving full prompts, private credentials, or large transient logs. | ||
|
|
||
| ## Verification | ||
|
|
||
| Regenerate the GIF: | ||
|
|
||
| ```bash | ||
| python make_demo_gif.py | ||
| ``` | ||
|
|
||
| Run the demo: | ||
|
|
||
| ```bash | ||
| python run_cross_skill_demo.py --backend file --reset | ||
| ``` | ||
|
|
||
| Run the focused offline tests: | ||
|
|
||
| ```bash | ||
| python -m unittest discover -s tests -v | ||
| ``` | ||
|
|
||
| Expected output includes: | ||
|
|
||
| ```text | ||
| MEMANTO ENGINEERING MEMORY | ||
| - Decision: Keep billing writes idempotent by Stripe event id. | ||
| - Preference: Add replay tests before changing webhook behavior. | ||
| - Quirk: Billing timestamps are stored as UTC ISO strings. | ||
| ``` | ||
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,123 @@ | ||
| """Generate a compact GIF for the Claude Code skills memory demo.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from pathlib import Path | ||
|
|
||
| from PIL import Image, ImageDraw, ImageFont | ||
|
|
||
| WIDTH = 1040 | ||
| HEIGHT = 640 | ||
| BG = (15, 19, 27) | ||
| PANEL = (27, 34, 45) | ||
| TEXT = (238, 244, 250) | ||
| MUTED = (149, 166, 184) | ||
| GREEN = (91, 205, 149) | ||
| BLUE = (112, 177, 255) | ||
|
|
||
|
|
||
| STEPS = [ | ||
| ( | ||
| "Skill run 1: /grill-with-docs", | ||
| [ | ||
| "Decision: Keep billing writes idempotent by Stripe event id.", | ||
| "Preference: Add replay tests before webhook changes.", | ||
| "Quirk: Billing timestamps are UTC ISO strings.", | ||
| ], | ||
| ), | ||
| ( | ||
| "after_skill hook", | ||
| [ | ||
| "Extract durable engineering memory.", | ||
| "Store only decisions, preferences, quirks, constraints.", | ||
| "Skip full prompts, secrets, and noisy logs.", | ||
| ], | ||
| ), | ||
| ( | ||
| "Skill run 2: /tdd starts fresh", | ||
| [ | ||
| "Task: add tests for Stripe webhook replay.", | ||
| "Files: stripe.ts, stripe.test.ts", | ||
| "Graph state is new; Memanto memory persists.", | ||
| ], | ||
| ), | ||
| ( | ||
| "before_skill hook", | ||
| [ | ||
| "MEMANTO ENGINEERING MEMORY", | ||
| "- Keep billing writes idempotent by Stripe event id.", | ||
| "- Add replay tests before changing webhook behavior.", | ||
| "- Billing timestamps are stored as UTC ISO strings.", | ||
| ], | ||
| ), | ||
| ( | ||
| "Prompt injection", | ||
| [ | ||
| "Append compact memory block to the next skill prompt.", | ||
| "The /tdd skill now sees the prior review decisions.", | ||
| "No manual context shoving between sessions.", | ||
| ], | ||
| ), | ||
| ] | ||
|
|
||
|
|
||
| def main() -> None: | ||
| """Render the animated walkthrough GIF into the local assets directory.""" | ||
| assets_dir = Path(__file__).parent / "assets" | ||
| assets_dir.mkdir(exist_ok=True) | ||
| output = assets_dir / "demo.gif" | ||
| frames: list[Image.Image] = [] | ||
| font = _font(30) | ||
| small = _font(23) | ||
| tiny = _font(20) | ||
|
|
||
| for index, (title, lines) in enumerate(STEPS, start=1): | ||
| for _ in range(6): | ||
| image = Image.new("RGB", (WIDTH, HEIGHT), BG) | ||
| draw = ImageDraw.Draw(image) | ||
| draw.rounded_rectangle((44, 44, WIDTH - 44, HEIGHT - 44), radius=18, fill=PANEL) | ||
| draw.text((82, 82), "Claude Code Skills + Memanto", font=font, fill=GREEN) | ||
| draw.text((82, 126), f"Step {index}/5: {title}", font=small, fill=TEXT) | ||
| draw.line((82, 174, WIDTH - 82, 174), fill=(63, 77, 94), width=2) | ||
|
|
||
| y = 218 | ||
| for line in lines: | ||
| color = BLUE if line.startswith("MEMANTO") else TEXT | ||
| draw.text((104, y), line, font=small, fill=color) | ||
| y += 50 | ||
|
|
||
| draw.text( | ||
| (82, HEIGHT - 96), | ||
| "Global active memory across isolated developer skills", | ||
| font=tiny, | ||
| fill=MUTED, | ||
| ) | ||
| frames.append(image) | ||
|
|
||
| frames[0].save( | ||
| output, | ||
| save_all=True, | ||
| append_images=frames[1:], | ||
| duration=1000, | ||
| loop=0, | ||
| optimize=True, | ||
| ) | ||
| print(output) | ||
|
|
||
|
|
||
| def _font(size: int) -> ImageFont.FreeTypeFont | ImageFont.ImageFont: | ||
| """Load a readable system font with a portable default fallback.""" | ||
| candidates = [ | ||
| "/System/Library/Fonts/SFNS.ttf", | ||
| "/System/Library/Fonts/Supplemental/Arial.ttf", | ||
| "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", | ||
| ] | ||
| for candidate in candidates: | ||
| path = Path(candidate) | ||
| if path.exists(): | ||
| return ImageFont.truetype(str(path), size=size) | ||
| return ImageFont.load_default() | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.