From 42afa121a6bdeffd44088e0784a9b9d0e2a52adb Mon Sep 17 00:00:00 2001 From: inline-arc Date: Fri, 26 Jun 2026 00:19:38 +0530 Subject: [PATCH 01/12] addup --- memory/cloud.test.py | 0 memory/llm.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 memory/cloud.test.py create mode 100644 memory/llm.py diff --git a/memory/cloud.test.py b/memory/cloud.test.py new file mode 100644 index 0000000..e69de29 diff --git a/memory/llm.py b/memory/llm.py new file mode 100644 index 0000000..e69de29 From 215ebb24595288b5f8456393e48cc143ee7536f1 Mon Sep 17 00:00:00 2001 From: inline-arc Date: Fri, 26 Jun 2026 17:15:18 +0530 Subject: [PATCH 02/12] events --- app.yml | 10 +++++----- index.js | 13 +++++++++++++ 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/app.yml b/app.yml index e5418ba..248b7fe 100644 --- a/app.yml +++ b/app.yml @@ -19,11 +19,11 @@ default_events: # - create # - delete - deployment - # - deployment_status + - deployment_status # - fork # - gollum - # - issue_comment - # - issues + - issue_comment + - issues # - label # - milestone # - member @@ -36,8 +36,8 @@ default_events: # - project_column # - public - pull_request -# - pull_request_review -# - pull_request_review_comment + - pull_request_review + - pull_request_review_comment # - push # - release # - repository diff --git a/index.js b/index.js index f52187c..e3539b4 100644 --- a/index.js +++ b/index.js @@ -45,6 +45,19 @@ export default (app) => { auto_inactive: true, // Adds a new inactive status to all prior non-transient, non-production environment deployments with the same repository and environment name as the created status's deployment. An inactive status is only added to deployments that had a success state. }), ); + + app.on('issue_comment.created', async (context) => { + const comment = context.payload.comment.body; + const botName = '@agentwaspai'; // replace with your actual bot's mention handle + + if (comment.includes(botName)) { + const params = context.issue({ + body: `Hey! You mentioned me. Here's what I can do...`, + }); + await context.octokit.issues.createComment(params); + } + }); + }, ); From ae5c289566eade0d019ee1c9cbb30e7cc2ce49ad Mon Sep 17 00:00:00 2001 From: inline-arc Date: Fri, 26 Jun 2026 23:42:40 +0530 Subject: [PATCH 03/12] first call --- app.yml | 9 ++- index.js | 143 ++++++++++++++++++++++++++------------------- test/index.test.js | 43 ++++++++++++++ 3 files changed, 134 insertions(+), 61 deletions(-) diff --git a/app.yml b/app.yml index 248b7fe..d1974ae 100644 --- a/app.yml +++ b/app.yml @@ -38,6 +38,8 @@ default_events: - pull_request - pull_request_review - pull_request_review_comment + - discussion # added this + - discussion_comment # added this # - push # - release # - repository @@ -70,7 +72,7 @@ default_permissions: # Issues and related comments, assignees, labels, and milestones. # https://developer.github.com/v3/apps/permissions/#permission-on-issues - # issues: write + issues: write # Search repositories, list collaborators, and access repository '. # https://developer.github.com/v3/apps/permissions/#metadata-permissions @@ -123,6 +125,11 @@ default_permissions: # Get notified of, and update, content references. # https://developer.github.com/v3/apps/permissions/ # organization_administration: read + + # Discussions and related comments. + # https://developer.github.com/v3/apps/permissions/#permission-on-discussions + discussions: write + # The name of the GitHub App. Defaults to the name specified in package.json # name: My Probot App diff --git a/index.js b/index.js index e3539b4..343482f 100644 --- a/index.js +++ b/index.js @@ -1,69 +1,92 @@ -// Deployments API example -// See: https://developer.github.com/v3/repos/deployments/ to learn more + // Deployments API example + // See: https://developer.github.com/v3/repos/deployments/ to learn more -/** - * This is the main entrypoint to your Probot app - * @param {import('probot').Probot} app - */ -export default (app) => { - // Your code here - app.log.info("Yay, the app was loaded!"); - app.on( - ["pull_request.opened", "pull_request.synchronize"], - async (context) => { - // Creates a deployment on a pull request event - // Then sets the deployment status to success - // NOTE: this example doesn't actually integrate with a cloud - // provider to deploy your app, it just demos the basic API usage. - app.log.info(context.payload); + /** + * This is the main entrypoint to your Probot app + * @param {import('probot').Probot} app + */ + export default (app) => { + // Your code here + app.log.info("Yay, the app was loaded!"); - // Probot API note: context.repo() => { username: 'hiimbex', repo: 'testing-things' } - const res = await context.octokit.rest.repos.createDeployment( - context.repo({ - ref: context.payload.pull_request.head.ref, // The ref to deploy. This can be a branch, tag, or SHA. - task: "deploy", // Specifies a task to execute (e.g., deploy or deploy:migrations). - auto_merge: true, // Attempts to automatically merge the default branch into the requested ref, if it is behind the default branch. - required_contexts: [], // The status contexts to verify against commit status checks. If this parameter is omitted, then all unique contexts will be verified before a deployment is created. To bypass checking entirely pass an empty array. Defaults to all unique contexts. - payload: { - schema: "rocks!", - }, // JSON payload with extra information about the deployment. Default: "" - environment: "production", // Name for the target deployment environment (e.g., production, staging, qa) - description: "My Probot App's first deploy!", // Short description of the deployment - transient_environment: false, // Specifies if the given environment is specific to the deployment and will no longer exist at some point in the future. - production_environment: true, // Specifies if the given environment is one that end-users directly interact with. - }), - ); - - const deploymentId = res.data.id; - await context.octokit.rest.repos.createDeploymentStatus( - context.repo({ - deployment_id: deploymentId, - state: "success", // The state of the status. Can be one of error, failure, inactive, pending, or success - log_url: "https://example.com", // The log URL to associate with this status. This URL should contain output to keep the user updated while the task is running or serve as historical information for what happened in the deployment. - description: "My Probot App set a deployment status!", // A short description of the status. - environment_url: "https://example.com", // Sets the URL for accessing your environment. - auto_inactive: true, // Adds a new inactive status to all prior non-transient, non-production environment deployments with the same repository and environment name as the created status's deployment. An inactive status is only added to deployments that had a success state. - }), - ); - - app.on('issue_comment.created', async (context) => { + // Mentions in issues/PR comments + app.on("issue_comment.created", async (context) => { + if (context.payload.comment.user?.type === "Bot") return; const comment = context.payload.comment.body; - const botName = '@agentwaspai'; // replace with your actual bot's mention handle - - if (comment.includes(botName)) { + const botName = "@agentwaspai"; + if (comment.toLowerCase().includes(botName.toLowerCase())) { const params = context.issue({ - body: `Hey! You mentioned me. Here's what I can do...`, + body: "Hey! You mentioned me. Here's what I can do...", }); - await context.octokit.issues.createComment(params); + await context.octokit.rest.issues.createComment(params); + } + }); + + // Mentions in Discussion comments + app.on("discussion_comment.created", async (context) => { + const comment = context.payload.comment.body; + const botName = "@agentwaspai"; + if (comment.toLowerCase().includes(botName.toLowerCase())) { + await context.octokit.graphql( + ` + mutation($discussionId: ID!, $body: String!) { + addDiscussionComment(input: { discussionId: $discussionId, body: $body }) { + comment { id } + } + } + `, + { + discussionId: context.payload.discussion.node_id, + body: "Hey! You mentioned me. Here's what I can do...", + }, + ); } - }); + }); + + app.on( + ["pull_request.opened", "pull_request.synchronize"], + async (context) => { + // Creates a deployment on a pull request event + // Then sets the deployment status to success + // NOTE: this example doesn't actually integrate with a cloud + // provider to deploy your app, it just demos the basic API usage. + + app.log.info(context.payload); + + // Probot API note: context.repo() => { username: 'hiimbex', repo: 'testing-things' } + const res = await context.octokit.rest.repos.createDeployment( + context.repo({ + ref: context.payload.pull_request.head.ref, // The ref to deploy. This can be a branch, tag, or SHA. + task: "deploy", // Specifies a task to execute (e.g., deploy or deploy:migrations). + auto_merge: true, // Attempts to automatically merge the default branch into the requested ref, if it is behind the default branch. + required_contexts: [], // The status contexts to verify against commit status checks. If this parameter is omitted, then all unique contexts will be verified before a deployment is created. To bypass checking entirely pass an empty array. Defaults to all unique contexts. + payload: { + schema: "rocks!", + }, // JSON payload with extra information about the deployment. Default: "" + environment: "production", // Name for the target deployment environment (e.g., production, staging, qa) + description: "My Probot App's first deploy!", // Short description of the deployment + transient_environment: false, // Specifies if the given environment is specific to the deployment and will no longer exist at some point in the future. + production_environment: true, // Specifies if the given environment is one that end-users directly interact with. + }), + ); - }, - ); + const deploymentId = res.data.id; + await context.octokit.rest.repos.createDeploymentStatus( + context.repo({ + deployment_id: deploymentId, + state: "success", // The state of the status. Can be one of error, failure, inactive, pending, or success + log_url: "https://example.com", // The log URL to associate with this status. This URL should contain output to keep the user updated while the task is running or serve as historical information for what happened in the deployment. + description: "My Probot App set a deployment status!", // A short description of the status. + environment_url: "https://example.com", // Sets the URL for accessing your environment. + auto_inactive: true, // Adds a new inactive status to all prior non-transient, non-production environment deployments with the same repository and environment name as the created status's deployment. An inactive status is only added to deployments that had a success state. + }), + ); + }, + ); - // For more information on building apps: - // https://probot.github.io/docs/ + // For more information on building apps: + // https://probot.github.io/docs/ - // To get your app running against GitHub, see: - // https://probot.github.io/docs/development/ -}; + // To get your app running against GitHub, see: + // https://probot.github.io/docs/development/ + }; diff --git a/test/index.test.js b/test/index.test.js index 4860988..b75e8a4 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -94,6 +94,49 @@ describe("My Probot app", () => { assert.deepStrictEqual(mock.pendingMocks(), []); }); + test("replies when the bot is mentioned in a comment", async () => { + const issueCommentPayload = { + action: "created", + comment: { + body: "hello @agentwaspai can you help?", + }, + issue: { + number: 1, + }, + repository: { + name: "testing-things", + owner: { + login: "hiimbex", + }, + }, + installation: { + id: 2, + }, + }; + + const mock = nock("https://api.github.com") + .post("/app/installations/2/access_tokens") + .reply(200, { + token: "test", + permissions: { + deployments: "write", + pull_requests: "read", + issues: "write", + }, + }) + .post("/repos/hiimbex/testing-things/issues/1/comments", (body) => { + assert.deepStrictEqual(body, { + body: "Hey! You mentioned me. Here's what I can do...", + }); + return true; + }) + .reply(200, { id: 456 }); + + await probot.receive({ name: "issue_comment", payload: issueCommentPayload }); + + assert.deepStrictEqual(mock.pendingMocks(), []); + }); + afterEach(() => { nock.cleanAll(); nock.enableNetConnect(); From 4d50020c82ff0e0315867b8a01f74df0f45f9a72 Mon Sep 17 00:00:00 2001 From: inline-arc Date: Wed, 1 Jul 2026 14:47:54 +0530 Subject: [PATCH 04/12] venv --- memory/__pycache__/cognee.cpython-311.pyc | Bin 0 -> 1182 bytes memory/api/main.py | 0 memory/cognee.py | 16 ++++++++++++++++ 3 files changed, 16 insertions(+) create mode 100644 memory/__pycache__/cognee.cpython-311.pyc create mode 100644 memory/api/main.py diff --git a/memory/__pycache__/cognee.cpython-311.pyc b/memory/__pycache__/cognee.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2254c1f7809562278157fe6639f1f866a901ab10 GIT binary patch literal 1182 zcma(QO-mb5aNcI~;rc<*P%&*`F9n4rYSkjGwFILjQIk}yR!IqK_9fYv?Ao^*zp4;W zN(vr&D0pm7meT&14df6W6$-tyH^G~yzTFtxh=tC~n>RCWX5PG+-B0c90x&pNg1Jr( z;D@C&fWHvV-Z9|_WROuFR_Z%iLAK7Va59IvoTCvWJI0tO^G!CVZF4o*+_ueg04qEP z80DOe3{2yXnewG!fMdiow(5Xb9rMuF^2<`A)Rn;nMOA3q%*<>uJ{MaIhbHHCy6e{d z`sTkM81Sd_lAfh4mfLJBG&!>~5_}d2u8z({A4Hay*5Ye3$?*J8Z0W`3(7HUj7>f?Z zhH-RoAiT9WoLopQO~ki`2jn1awK4?b;pFt>a_XWcd$anr&(_&D#-;(Wo*9=_1LQ7c z8GYrN-?Y!V`r5GwM~*iLNHL z<@40JP88LkydA(%H^F)_Z|K&!Em$oS#q*Jghl%GpCVE0LvJy$m7Ih_+n6R@a>X)pO z1k0_F?F&7yzD@apq^P8e<*=Hhe_-q6K@J*R-2w`^8+op{>I>BF^($5-|EOq)&} zjpH*`A{!F1er@WO^ldezXq3}*>c;AZLbUpGsDl*M&p^D)jdU`0?inj+axss`iI1t) z_p0lRvCIh7puJ>c4LV9T)}WsWT^mgiSssdeA=rS8*7xfzqFD8os literal 0 HcmV?d00001 diff --git a/memory/api/main.py b/memory/api/main.py new file mode 100644 index 0000000..e69de29 diff --git a/memory/cognee.py b/memory/cognee.py index e69de29..e4399e8 100644 --- a/memory/cognee.py +++ b/memory/cognee.py @@ -0,0 +1,16 @@ +import cognee +import asyncio +import os + +os.environ["LLM_PROVIDER"] = "gemini" +os.environ["LLM_MODEL"] = "gemini/gemini-2.0-flash" +os.environ["LLM_API_KEY"] = "AQ.Ab8RN6JYWkPkL_IS3OWXw3pd8VON1O4eN10IxV4_T_WCPx40dA" # your key here + +async def main(): + await cognee.forget(everything=True) + await cognee.remember("The Eiffel Tower is located in Paris, France.") + results = await cognee.recall(query_text="Where is the Eiffel Tower?") + for result in results: + print(result.text) + +asyncio.run(main()) \ No newline at end of file From b1244aa981db872f053b4542966cdf4a1348fcd5 Mon Sep 17 00:00:00 2001 From: inline-arc Date: Wed, 1 Jul 2026 15:17:21 +0530 Subject: [PATCH 05/12] rules.txt --- memory/rules.txt | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 memory/rules.txt diff --git a/memory/rules.txt b/memory/rules.txt new file mode 100644 index 0000000..dd0caa2 --- /dev/null +++ b/memory/rules.txt @@ -0,0 +1,44 @@ +# AgentWasp AI Code Review Rules + +## Response Format +- Always respond in markdown format +- Use headers (##) to separate sections +- Use bullet points for lists of issues +- Use code blocks (```) for any code references +- End every review with a ## Summary section + +## Review Structure +Every code review must follow this structure: +### Overview +Brief description of what this PR does. + +### Issues Found +List bugs, security risks, or breaking changes. + +### Suggestions +Non-blocking improvements or best practices. + +### Positives +What was done well in this PR. + +### Summary +One paragraph verdict: approve, request changes, or needs discussion. + +## Code Review Standards +- Always check for security vulnerabilities first +- Flag any hardcoded secrets or API keys immediately +- Check for proper error handling on all async functions +- Verify that new functions have proper input validation +- Flag N+1 query patterns in database calls +- Check that environment variables are used instead of hardcoded values + +## Response Tone +- Be constructive, not critical +- Always explain WHY something is an issue +- Suggest HOW to fix it, not just that it needs fixing +- Be concise — developers are busy + +## Language +- Always respond in English +- Use technical terms correctly +- Format code snippets with the correct language tag (```js, ```python, etc.) \ No newline at end of file From da21db32fb67f8a6db2502dd6c5c1ff852e6eece Mon Sep 17 00:00:00 2001 From: inline-arc Date: Wed, 1 Jul 2026 15:45:15 +0530 Subject: [PATCH 06/12] new rules.txt --- index.js | 5 +++-- intro.js | 15 +++++++++++++ memory/rules.txt | 55 ++++++++++++++++++++++++++++-------------------- 3 files changed, 50 insertions(+), 25 deletions(-) create mode 100644 intro.js diff --git a/index.js b/index.js index 343482f..c32032d 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,6 @@ // Deployments API example // See: https://developer.github.com/v3/repos/deployments/ to learn more - +import { INTRO_MESSAGE } from "./intro.js"; /** * This is the main entrypoint to your Probot app * @param {import('probot').Probot} app @@ -26,6 +26,7 @@ app.on("discussion_comment.created", async (context) => { const comment = context.payload.comment.body; const botName = "@agentwaspai"; + const INTRO_MESSAGE = ""; if (comment.toLowerCase().includes(botName.toLowerCase())) { await context.octokit.graphql( ` @@ -37,7 +38,7 @@ `, { discussionId: context.payload.discussion.node_id, - body: "Hey! You mentioned me. Here's what I can do...", + body: "INTRO_MESSAGE", }, ); } diff --git a/intro.js b/intro.js new file mode 100644 index 0000000..febd5ec --- /dev/null +++ b/intro.js @@ -0,0 +1,15 @@ +export const INTRO_MESSAGE = `**AgentWasp AI** ( AI code reviewer with persistent memory ) + +- Automatically reviews every PR using your codebase history +- Remembers past bugs, patterns and review feedback over time +- Answers questions about your code and diffs & updates + +**Commands:** +| Command | Description | +|---|---| +| \`/ask \` | Ask anything about this PR or codebase | +| \`/review\` | Trigger a manual review | +| \`@agentwaspai \` | Mention me anywhere in a comment | + +> Built with [Cognee](https://cognee.ai) · [Contribute](https://github.com/inline-arc/AgentwaspAi) +`; \ No newline at end of file diff --git a/memory/rules.txt b/memory/rules.txt index dd0caa2..4361ce7 100644 --- a/memory/rules.txt +++ b/memory/rules.txt @@ -1,44 +1,53 @@ -# AgentWasp AI Code Review Rules +# AgentWasp AI — Code Review Rules ## Response Format -- Always respond in markdown format -- Use headers (##) to separate sections +- Always respond in markdown +- Use ## headers to separate sections - Use bullet points for lists of issues -- Use code blocks (```) for any code references -- End every review with a ## Summary section +- Use code blocks with correct language tags (js, python, tsx, etc.) +- Every review must end with a Summary section ## Review Structure -Every code review must follow this structure: +Every code review must follow this exact structure: + ### Overview -Brief description of what this PR does. +Brief description of what this PR does and what files it touches. -### Issues Found -List bugs, security risks, or breaking changes. +### Issues +List bugs, security risks, breaking changes, or logic errors. +For each issue include: +- What the problem is +- Why it is a problem +- How to fix it ### Suggestions -Non-blocking improvements or best practices. +Non-blocking improvements, performance notes, or best practices. ### Positives -What was done well in this PR. +What was done well. Keep this brief and specific. ### Summary One paragraph verdict: approve, request changes, or needs discussion. +State clearly what must be resolved before merging. -## Code Review Standards -- Always check for security vulnerabilities first -- Flag any hardcoded secrets or API keys immediately -- Check for proper error handling on all async functions -- Verify that new functions have proper input validation +## Code Standards +- Flag hardcoded secrets, API keys, or credentials immediately +- Check all async functions have proper error handling +- Flag missing input validation on any function that accepts external data - Flag N+1 query patterns in database calls - Check that environment variables are used instead of hardcoded values +- Flag any direct use of eval() or dangerous equivalents +- Check that new functions are consistent with patterns already in the codebase ## Response Tone -- Be constructive, not critical -- Always explain WHY something is an issue -- Suggest HOW to fix it, not just that it needs fixing -- Be concise — developers are busy +- Be direct and specific +- Always explain why something is an issue, not just that it is one +- Always suggest how to fix it +- Do not pad responses with filler or compliments +- Keep reviews concise — flag what matters, skip what does not ## Language -- Always respond in English -- Use technical terms correctly -- Format code snippets with the correct language tag (```js, ```python, etc.) \ No newline at end of file +- English only +- Use correct technical terminology +- No informal language +- No emojis \ No newline at end of file From 14fcecd3b85a315e89abd3960cce5457ccf18cdb Mon Sep 17 00:00:00 2001 From: inline-arc Date: Wed, 1 Jul 2026 18:28:05 +0530 Subject: [PATCH 07/12] more rules --- index.js | 5 ++--- memory/rules/ask.txt | 18 ++++++++++++++++++ memory/rules/review.txt | 0 memory/{ => rules}/rules.txt | 0 4 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 memory/rules/ask.txt create mode 100644 memory/rules/review.txt rename memory/{ => rules}/rules.txt (100%) diff --git a/index.js b/index.js index c32032d..fe60ea6 100644 --- a/index.js +++ b/index.js @@ -16,7 +16,7 @@ import { INTRO_MESSAGE } from "./intro.js"; const botName = "@agentwaspai"; if (comment.toLowerCase().includes(botName.toLowerCase())) { const params = context.issue({ - body: "Hey! You mentioned me. Here's what I can do...", + body: INTRO_MESSAGE, }); await context.octokit.rest.issues.createComment(params); } @@ -26,7 +26,6 @@ import { INTRO_MESSAGE } from "./intro.js"; app.on("discussion_comment.created", async (context) => { const comment = context.payload.comment.body; const botName = "@agentwaspai"; - const INTRO_MESSAGE = ""; if (comment.toLowerCase().includes(botName.toLowerCase())) { await context.octokit.graphql( ` @@ -38,7 +37,7 @@ import { INTRO_MESSAGE } from "./intro.js"; `, { discussionId: context.payload.discussion.node_id, - body: "INTRO_MESSAGE", + body: INTRO_MESSAGE, }, ); } diff --git a/memory/rules/ask.txt b/memory/rules/ask.txt new file mode 100644 index 0000000..664fa88 --- /dev/null +++ b/memory/rules/ask.txt @@ -0,0 +1,18 @@ +# AgentWasp AI — Ask Rules + +## Answer Structure +- Answer the question directly in the first sentence +- Provide context or explanation after +- Keep answers under 200 words unless the question requires more +- Do not volunteer a full review when a specific question was asked + +## Code References +- Always use a code block when referencing code +- Always use the correct language tag +- Show the fix or example, not just a description of it + +## Context Handling +- Ground the answer in the actual diff when available +- If referencing past PRs from memory, mention the PR number +- If the question cannot be answered confidently, say so explicitly +- Do not guess or invent an answer \ No newline at end of file diff --git a/memory/rules/review.txt b/memory/rules/review.txt new file mode 100644 index 0000000..e69de29 diff --git a/memory/rules.txt b/memory/rules/rules.txt similarity index 100% rename from memory/rules.txt rename to memory/rules/rules.txt From fd98e80616628f41b125975ece71ecef9ec332df Mon Sep 17 00:00:00 2001 From: inline-arc Date: Thu, 2 Jul 2026 01:15:15 +0530 Subject: [PATCH 08/12] yaml --- deploy.yml | 0 k8s/cognee-deployment.yml | 115 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 115 insertions(+) create mode 100644 deploy.yml create mode 100644 k8s/cognee-deployment.yml diff --git a/deploy.yml b/deploy.yml new file mode 100644 index 0000000..e69de29 diff --git a/k8s/cognee-deployment.yml b/k8s/cognee-deployment.yml new file mode 100644 index 0000000..dbcb1d8 --- /dev/null +++ b/k8s/cognee-deployment.yml @@ -0,0 +1,115 @@ +# k8s/cognee-deployment.yaml +# Runs ONLY the Cognee memory service. +# Your FastAPI app lives in k8s/api-deployment.yaml + +--- +apiVersion: v1 +kind: Namespace +metadata: + name: agentwaspai + +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: cognee-data + namespace: agentwaspai +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 5Gi + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: cognee + namespace: agentwaspai + labels: + app: cognee +spec: + replicas: 1 + selector: + matchLabels: + app: cognee + template: + metadata: + labels: + app: cognee + spec: + containers: + - name: cognee + image: cognee/cognee:latest + imagePullPolicy: Always + ports: + - containerPort: 8000 + env: + - name: LLM_API_KEY + valueFrom: + secretKeyRef: + name: agentwaspai-secrets + key: LLM_API_KEY + - name: LLM_MODEL + valueFrom: + secretKeyRef: + name: agentwaspai-secrets + key: LLM_MODEL + - name: EMBEDDING_MODEL + valueFrom: + secretKeyRef: + name: agentwaspai-secrets + key: EMBEDDING_MODEL + - name: EMBEDDING_API_KEY + valueFrom: + secretKeyRef: + name: agentwaspai-secrets + key: EMBEDDING_API_KEY + - name: SYSTEM_ROOT_DIRECTORY + value: /data/cognee_system + - name: DATA_ROOT_DIRECTORY + value: /data/cognee_data + - name: TELEMETRY_DISABLED + value: "true" + volumeMounts: + - name: cognee-storage + mountPath: /data + resources: + requests: + memory: 512Mi + cpu: 250m + limits: + memory: 2Gi + cpu: 1000m + readinessProbe: + httpGet: + path: / + port: 8000 + initialDelaySeconds: 10 + periodSeconds: 5 + livenessProbe: + httpGet: + path: / + port: 8000 + initialDelaySeconds: 30 + periodSeconds: 15 + volumes: + - name: cognee-storage + persistentVolumeClaim: + claimName: cognee-data + +--- +apiVersion: v1 +kind: Service +metadata: + name: cognee + namespace: agentwaspai +spec: + selector: + app: cognee + ports: + - protocol: TCP + port: 8000 + targetPort: 8000 + type: ClusterIP \ No newline at end of file From b845cf52244a734e31d9966f579aedfe37d37d30 Mon Sep 17 00:00:00 2001 From: inline-arc Date: Fri, 3 Jul 2026 16:30:47 +0530 Subject: [PATCH 09/12] cloud --- k8s/cognee-deployment.yml | 2 +- memory/__pycache__/cognee.cpython-311.pyc | Bin 1182 -> 1269 bytes memory/api/__pycache__/main.cpython-311.pyc | Bin 0 -> 3609 bytes memory/api/main.py | 58 ++++++++++++++++ memory/cognee.py | 16 ----- memory/main.py | 29 ++++++++ memory/rules/claude.md | 73 ++++++++++++++++++++ 7 files changed, 161 insertions(+), 17 deletions(-) create mode 100644 memory/api/__pycache__/main.cpython-311.pyc delete mode 100644 memory/cognee.py create mode 100644 memory/main.py create mode 100644 memory/rules/claude.md diff --git a/k8s/cognee-deployment.yml b/k8s/cognee-deployment.yml index dbcb1d8..b560345 100644 --- a/k8s/cognee-deployment.yml +++ b/k8s/cognee-deployment.yml @@ -41,7 +41,7 @@ spec: spec: containers: - name: cognee - image: cognee/cognee:latest + image: cognee/cognee:0.5.1 imagePullPolicy: Always ports: - containerPort: 8000 diff --git a/memory/__pycache__/cognee.cpython-311.pyc b/memory/__pycache__/cognee.cpython-311.pyc index 2254c1f7809562278157fe6639f1f866a901ab10..3d07e82e1279741ff1763ad11d78f2f1d37d1f09 100644 GIT binary patch literal 1269 zcmbVM-D?v;5TCtE(wH`CjTKRpdlCXBO?$mW#ZOw=2fsj3p+w4Nchg)=?&97?O;rde z5+RR*|3O9UAEVHRaNv^&`gT(L;*)bp6AUPdv$wySS!U)pv&`L_WHJFLuDi4r(E&b% z#y}#w%JvmhUIPbCs<7ODlV!rS=di44AYct6`+}CXf$F=#0c;SoaQB5^j+>CyzlP>D zwtL!pg>27oZzs_EG7w}`!#IGoq0W(cQ{T`qFYdlP!>S(Zx8b$^B0@&vhCV#PqnojR zjvMyG$2Q}mUHr=S6rR8W+?{+%)Sf{#08hwh1cA`-_hIpH8b87;9c#xwtlIi zRNXr>J*|X89hG6$Y~ISvuuQqYcxH|XmMInH@)>UB=5l$F&(G#_S^Ch+GI!MxB1>)m zqG_6CZsrQ*Q&vuJiqYmQJ!P8WG@mKW3>%B@i)Jma3@KCBA9DvVW0>lK4ZL6z+~hV2-t zA`BM#XZ?L#lxd@?RybAE6Dm#oiCuNYp*68?+^JAya2HelmgxdUBey}SQL5Y@+H-vHrz2m$EPzrfA}j8Ar^yYZ#&)QSHJpE~;LYByyCx;BN>2;V~p zK->3cXe|XXNF51)L=W|#9s3whw#zS*on#x=P7JHsN*w)=IJT8I_CE5*XFHtsbOeXT zYdXw!W%$$SU6QZMhVvHW!SLV!!jJgbn#&tiab6xqGyLkkMT9^ngmhtVtGBw4Z1q+U jnIgvn7(y1vL~HE%czdC}@N%)U_yJN|ka|COdOSY?(Oxja literal 1182 zcma(QO-mb5aNcI~;rc<*P%&*`F9n4rYSkjGwFILjQIk}yR!IqK_9fYv?Ao^*zp4;W zN(vr&D0pm7meT&14df6W6$-tyH^G~yzTFtxh=tC~n>RCWX5PG+-B0c90x&pNg1Jr( z;D@C&fWHvV-Z9|_WROuFR_Z%iLAK7Va59IvoTCvWJI0tO^G!CVZF4o*+_ueg04qEP z80DOe3{2yXnewG!fMdiow(5Xb9rMuF^2<`A)Rn;nMOA3q%*<>uJ{MaIhbHHCy6e{d z`sTkM81Sd_lAfh4mfLJBG&!>~5_}d2u8z({A4Hay*5Ye3$?*J8Z0W`3(7HUj7>f?Z zhH-RoAiT9WoLopQO~ki`2jn1awK4?b;pFt>a_XWcd$anr&(_&D#-;(Wo*9=_1LQ7c z8GYrN-?Y!V`r5GwM~*iLNHL z<@40JP88LkydA(%H^F)_Z|K&!Em$oS#q*Jghl%GpCVE0LvJy$m7Ih_+n6R@a>X)pO z1k0_F?F&7yzD@apq^P8e<*=Hhe_-q6K@J*R-2w`^8+op{>I>BF^($5-|EOq)&} zjpH*`A{!F1er@WO^ldezXq3}*>c;AZLbUpGsDl*M&p^D)jdU`0?inj+axss`iI1t) z_p0lRvCIh7puJ>c4LV9T)}WsWT^mgiSssdeA=rS8*7xfzqFD8os diff --git a/memory/api/__pycache__/main.cpython-311.pyc b/memory/api/__pycache__/main.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..68587161e7cd6ef07d754ba70b790aa95389902a GIT binary patch literal 3609 zcmcH*O>Y~=b!KH9-$#^;|F+%!QI6N+e>cOl8`(z9kXz6kfk2J0x<2u;2s2 zLS}GF1|Gb7yiURE0AA+-UYMO^(FaPh%k0?fZb-C#S??C?{3*PCW2adE17!<-o;FA% z*#pv%=5GtaV*8{|v%v$>NB*Pq&_3x=Hhe%jYWA|DcZ4SR5a>_N_9gp3Pkp!h8yjKA z*l{+(zPEj1OM!jE>o?G^d{eyfGv6R7PQ4BzpD#FuV-@l?{K`}L<(atTY14-7Onvl$ z7yilY>__k4OPh;01USKW4cok4U}n|}Oj~A_twi1{EaXjdAjtZPr(17FMbHfLt>I7Wh5!hq19zZnZ)F6WuI*o7BB-$y#x|W zyGi1@*L}mxnYmw>{D%2S(X^ehZ_sutv03w;GvX9RY^T7@5i36eqhNbaBrlY+@~Nzu zUvM&B2Q%$7x5VK**>*3h2j=@!rxoMShNy3!^)2> zF7r;D8bKId`z-+FmZugzvNpH==H|HjgR5@%S|xm~8opNUb?G&~R~iT(Xw36}(0%xZ znKrW7hUPhLb!A9szG$0FsNXbrIF}WBktR?Zgj2^ovTLY%0n)l^LwR%_%q^UUPsP!G6g0K z?(a`A_=3$bbUgLWUHtJ>ZaPJtPd9S|Fg198i}`K`kgJ4(A3H64rI54&%&xTOj%}%Z zwUTmQVq{hMm2#KfCHLf?k-Ic5&sZ;Ew-BctHspY!ok~Msdnz*>!^*}3p0)s(15u&+ zuIdGwseogu7rKfE_))}k?J13uHwll6iQPC%=`ovc^U1{=LXmuF{0I|^le;+W-*IGo|0!g)mO1674G;B-R{+% z>My~^KpORFqj&o%c@~VUUvTvSmktQ0T?r=mUe@!pMPoTzFj$*gqT`6ITn6NzTQXWA zx0`O-?kKHJ2n7R@usDD+Q+$ft$zKeT(yPKm|B~ z49sNyslL{t>mP1vk2)*b8&&NMmmbnrrnPHy&S6rJIZ~v?*2N_Bt?Mod`;3mJ9q!w z*PYSz>5b_>z4uuC~2qNL){ELzFg@Uul9`B zHAM;334rIaDl0Dufckf!iwqpE#^O7st6#!SF9~PFD*~45RBA=X?>nH46%8&v_*j#W z3TNZ8r(_DY<0*>;?w|!QR^z&#F;QcF+C<%m+$l;9AHiRwf%XOdI^uh}6*q$eR8bTK z^#YEK;jQK4A`~pRwQw0op6VT4W@-U?}a>8xz&q%-9-k*_hSv)oJx_el!k<;#By{;0itGwX$p4o}H>cxtBv8rAy z%XO8~>sT=kh3nRH!Fk!r*D0MB5{JR{4^U#BqC}Ig2Pjsh1F(lkfzM+1V{3yI8m-c3 zU7~al_u_-+GLd?lWE}?&+#gswTcQ0`3dK>PN9&{kaB!?i4mAeHD-@>atxFyBM|IKw zIJmD#9wtiX+RESML02BE(eCmwe4f$Ha_qrtYsn4kG5hL*OD8LIvPvfb@q1k($`DA1 GaQHWB1T~QW literal 0 HcmV?d00001 diff --git a/memory/api/main.py b/memory/api/main.py index e69de29..9acc525 100644 --- a/memory/api/main.py +++ b/memory/api/main.py @@ -0,0 +1,58 @@ +from __future__ import annotations + +from typing import Any + +import cognee +from fastapi import FastAPI, HTTPException +from pydantic import BaseModel, Field + + +app = FastAPI(title="Cognee Memory API", version="1.0.0") + + +class RememberRequest(BaseModel): + text: str = Field(..., min_length=1, description="Text to store in memory") + + +class RecallRequest(BaseModel): + query_text: str = Field(..., min_length=1, description="Text used to search memory") + + +async def _call_cognee(method_name: str, *args: Any, **kwargs: Any) -> Any: + method = getattr(cognee, method_name) + try: + return await method(*args, **kwargs) + except Exception as exc: # pragma: no cover - surfaced through the API response + raise HTTPException(status_code=500, detail=str(exc)) from exc + + +@app.get("/health") +async def health() -> dict[str, str]: + return {"status": "ok"} + + +@app.post("/remember") +async def remember(payload: RememberRequest) -> dict[str, str]: + await _call_cognee("remember", payload.text) + return {"message": "saved"} + + +@app.post("/recall") +async def recall(payload: RecallRequest) -> dict[str, Any]: + results = await _call_cognee("recall", query_text=payload.query_text) + items = [] + for result in results: + items.append( + { + "text": getattr(result, "text", str(result)), + "score": getattr(result, "score", None), + "metadata": getattr(result, "metadata", None), + } + ) + return {"query_text": payload.query_text, "results": items} + + +if __name__ == "__main__": + import uvicorn + + uvicorn.run(app, host="0.0.0.0", port=8000) diff --git a/memory/cognee.py b/memory/cognee.py deleted file mode 100644 index e4399e8..0000000 --- a/memory/cognee.py +++ /dev/null @@ -1,16 +0,0 @@ -import cognee -import asyncio -import os - -os.environ["LLM_PROVIDER"] = "gemini" -os.environ["LLM_MODEL"] = "gemini/gemini-2.0-flash" -os.environ["LLM_API_KEY"] = "AQ.Ab8RN6JYWkPkL_IS3OWXw3pd8VON1O4eN10IxV4_T_WCPx40dA" # your key here - -async def main(): - await cognee.forget(everything=True) - await cognee.remember("The Eiffel Tower is located in Paris, France.") - results = await cognee.recall(query_text="Where is the Eiffel Tower?") - for result in results: - print(result.text) - -asyncio.run(main()) \ No newline at end of file diff --git a/memory/main.py b/memory/main.py new file mode 100644 index 0000000..abd7262 --- /dev/null +++ b/memory/main.py @@ -0,0 +1,29 @@ +import asyncio +from dotenv import load_dotenv +import os +import cognee + +async def main(): + # Connect to Cognee Cloud + await cognee.serve( + url=os.getenv("COGNEE_API_URL"), + api_key=os.getenv("COGNEE_API_KEY") + ) + + # Store content in memory — ingests, builds knowledge graph, enriches + await cognee.remember( + "Cognee Cloud automates knowledge graph creation in the cloud.", + dataset_name="default_dataset", + ) + + # Retrieve from memory + results = await cognee.recall( + query_text="What does Cognee Cloud automate?", + ) + for result in results: + print(result) + + # Disconnect when done + await cognee.disconnect() + +asyncio.run(main()) \ No newline at end of file diff --git a/memory/rules/claude.md b/memory/rules/claude.md new file mode 100644 index 0000000..3181945 --- /dev/null +++ b/memory/rules/claude.md @@ -0,0 +1,73 @@ +# Cognee Cloud Memory Skill + +This skill connects Claude Code to Cognee Cloud for persistent knowledge graph memory. + +## Prerequisites +- Cognee Cloud account with API key +- Environment variables set: + - `COGNEE_BASE_URL` — your tenant API endpoint + - `COGNEE_API_KEY` — your API key + +**If these variables are not set**, ask the user to run the export commands from the Cognee Cloud console (Connect to Claude Code → Step 1). + +## ALWAYS ping Cognee Cloud first + +Before any other operation in the conversation, ping Cognee Cloud to confirm the env vars are valid and the tenant is reachable. If the ping fails (non-200, network error, or auth error), tell the user immediately and ask them to re-export the credentials from the Cognee Cloud console — do NOT proceed with remember/recall calls against a broken connection. + +```bash +curl -fsS -o /dev/null -w "%{http_code}" \ + "$COGNEE_BASE_URL/api/v1/datasets/" \ + -H "X-Api-Key: $COGNEE_API_KEY" +``` +A `200` means the connection works. A `401` means the API key is wrong; `404`/`5xx` means the tenant URL is wrong or the service is down. + +## Session ID — ALWAYS use one + +At the start of the conversation, generate ONE id (your agent name + a unix timestamp) and reuse it as `session_id` in every call. Sessions group your activity in the Cognee Cloud dashboard and are converted into long-term memory. The ONLY exception: when the user explicitly asks you to store something directly in the knowledge graph, call /remember without a session_id. + +## Operations + +### Remember — Store knowledge +When the user shares important information worth preserving: +```bash +# Default: store as a session entry — always include your session_id +curl -X POST $COGNEE_BASE_URL/api/v1/remember/entry \ + -H "X-Api-Key: $COGNEE_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"entry": {"type": "qa", "question": "", "answer": ""}, "dataset_name": "default_dataset", "session_id": ""}' +``` + +### Store directly in the knowledge graph (ONLY when explicitly asked) +Use this only when the user explicitly asks to store something in the graph / permanent memory. The data must be a FILE upload (inline text is rejected with 422) and must NOT include a session_id: +```bash +TMP=$(mktemp) && printf '%s' "" > "$TMP" +curl -X POST $COGNEE_BASE_URL/api/v1/remember \ + -H "X-Api-Key: $COGNEE_API_KEY" \ + -F "data=@$TMP;type=text/plain" \ + -F "datasetName=default_dataset" +rm -f "$TMP" +``` + +### Recall — Retrieve knowledge +Before answering questions, check if relevant knowledge exists: +```bash +curl -X POST $COGNEE_BASE_URL/api/v1/recall \ + -H "X-Api-Key: $COGNEE_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"query": "", "session_id": ""}' +``` + +For targeted retrieval, add "search_type" to the recall body — one of: GRAPH_COMPLETION (default), CHUNKS, GRAPH_SUMMARY_COMPLETION, HYBRID_COMPLETION. + +### List datasets +```bash +curl -s "$COGNEE_BASE_URL/api/v1/datasets/?session_id=" -H "X-Api-Key: $COGNEE_API_KEY" +``` + +## Behavior +1. At session start, verify COGNEE_BASE_URL and COGNEE_API_KEY are set; if not, ask the user to export them +2. **Ping Cognee Cloud** with the curl above before any other operation. If it doesn't return 200, surface the failure to the user and stop — do not proceed with broken credentials +3. Generate one session id for the conversation (your agent name + a unix timestamp) and pass it as `session_id` in every call — the session appears automatically in the Cognee Cloud dashboard under Sessions and is converted into long-term memory +4. Use recall before answering to check for relevant context +5. Use /remember/entry (with session_id) to store important information; only when the user explicitly asks to store directly in the graph, use /remember (file upload, no session_id) +6. Use default_dataset unless specified otherwi \ No newline at end of file From ff7cdbbe7295cfffbd2eca3e447a047ae1518520 Mon Sep 17 00:00:00 2001 From: inline-arc Date: Sat, 4 Jul 2026 13:18:26 +0530 Subject: [PATCH 10/12] call --- memory/config/rules.py | 11 +++++++++++ memory/main.py | 29 ++++++++++++++++++++++------- 2 files changed, 33 insertions(+), 7 deletions(-) create mode 100644 memory/config/rules.py diff --git a/memory/config/rules.py b/memory/config/rules.py new file mode 100644 index 0000000..7e2e660 --- /dev/null +++ b/memory/config/rules.py @@ -0,0 +1,11 @@ +from pathlib import Path + +RULES_DIR = Path(__file__).parent.parent / "rules" + +def load_rules(filename: str) -> str: + return (RULES_DIR / filename).read_text(encoding="utf-8") + +ASK_RULES = load_rules("ask.txt"); +REVIEW_RULES = load_rules("review.txt"); +SYSTEM_RULES = load_rules("rules.txt"); +CLAUDE_RULES = load_rules("claude.md"); \ No newline at end of file diff --git a/memory/main.py b/memory/main.py index abd7262..5571580 100644 --- a/memory/main.py +++ b/memory/main.py @@ -1,28 +1,43 @@ import asyncio from dotenv import load_dotenv +from config.rules import REVIEW_RULES, SYSTEM_RULES, CLAUDE_RULES, ASK_RULES import os import cognee +load_dotenv() + async def main(): - # Connect to Cognee Cloud + # Connect to Cognee Cloud @agentwaspai /deploy cognee-cloud await cognee.serve( url=os.getenv("COGNEE_API_URL"), api_key=os.getenv("COGNEE_API_KEY") ) - # Store content in memory — ingests, builds knowledge graph, enriches + # Store content in memory — ingests, builds knowledge graph, enriches @agentwaspai /remember await cognee.remember( - "Cognee Cloud automates knowledge graph creation in the cloud.", dataset_name="default_dataset", ) - # Retrieve from memory + #agent-memory @agentwaspai setup + await cognee.agent_memory( + dataset_name="default_dataset", + with_memory_query_fixed=True, + save_trace=True, + memory_query_fixed=CLAUDE_RULES, + memory_query_fixed_prompt=REVIEW_RULES, + ) + + # Retrieve from memory @agentwaspai /review results = await cognee.recall( - query_text="What does Cognee Cloud automate?", + dataset_name="default_dataset", + query_text=ASK_RULES, + top_k=5 ) - for result in results: - print(result) + print("Recall Results:") + for r in results: + print(r) + # Disconnect when done await cognee.disconnect() From 43ad77a504e9fd0735ab84ae16dff6da70fea2cb Mon Sep 17 00:00:00 2001 From: inline-arc Date: Mon, 6 Jul 2026 11:00:58 +0530 Subject: [PATCH 11/12] metadata --- index.js | 165 +++++++++--------- .../config/__pycache__/rules.cpython-311.pyc | Bin 0 -> 855 bytes memory/main.py | 154 +++++++++++++--- memory/rules/review.txt | 38 ++++ test/index.test.js | 138 +++++---------- 5 files changed, 299 insertions(+), 196 deletions(-) create mode 100644 memory/config/__pycache__/rules.cpython-311.pyc diff --git a/index.js b/index.js index fe60ea6..9ab104a 100644 --- a/index.js +++ b/index.js @@ -1,92 +1,97 @@ - // Deployments API example - // See: https://developer.github.com/v3/repos/deployments/ to learn more -import { INTRO_MESSAGE } from "./intro.js"; - /** - * This is the main entrypoint to your Probot app - * @param {import('probot').Probot} app - */ - export default (app) => { - // Your code here - app.log.info("Yay, the app was loaded!"); +const BOT_NAME = "@agentwaspai"; + +export default (app) => { + app.log.info("AgentWasp AI loaded."); - // Mentions in issues/PR comments app.on("issue_comment.created", async (context) => { if (context.payload.comment.user?.type === "Bot") return; - const comment = context.payload.comment.body; - const botName = "@agentwaspai"; - if (comment.toLowerCase().includes(botName.toLowerCase())) { - const params = context.issue({ - body: INTRO_MESSAGE, - }); - await context.octokit.rest.issues.createComment(params); - } - }); - // Mentions in Discussion comments - app.on("discussion_comment.created", async (context) => { - const comment = context.payload.comment.body; - const botName = "@agentwaspai"; - if (comment.toLowerCase().includes(botName.toLowerCase())) { - await context.octokit.graphql( - ` - mutation($discussionId: ID!, $body: String!) { - addDiscussionComment(input: { discussionId: $discussionId, body: $body }) { - comment { id } - } - } - `, - { - discussionId: context.payload.discussion.node_id, - body: INTRO_MESSAGE, - }, + const body = context.payload.comment.body.trim(); + + if (!body.toLowerCase().includes(BOT_NAME.toLowerCase())) return; + if (!body.toLowerCase().includes("/review")) return; + + if (!context.payload.issue.pull_request) { + await context.octokit.rest.issues.createComment( + context.issue({ body: "This command only works on pull requests." }) ); + return; } - }); - app.on( - ["pull_request.opened", "pull_request.synchronize"], - async (context) => { - // Creates a deployment on a pull request event - // Then sets the deployment status to success - // NOTE: this example doesn't actually integrate with a cloud - // provider to deploy your app, it just demos the basic API usage. + const pr = context.payload.issue; + const number = pr.number; + + try { + // fetch everything in parallel + const [ + { data: diff }, + { data: files }, + { data: comments }, + { data: reviewComments }, + { data: reviews }, + { data: commits }, + ] = await Promise.all([ + context.octokit.rest.pulls.get({ + ...context.repo(), + pull_number: number, + mediaType: { format: "diff" }, + }), + context.octokit.rest.pulls.listFiles({ + ...context.repo(), + pull_number: number, + }), + context.octokit.rest.issues.listComments({ + ...context.repo(), + issue_number: number, + }), + context.octokit.rest.pulls.listReviewComments({ + ...context.repo(), + pull_number: number, + }), + context.octokit.rest.pulls.listReviews({ + ...context.repo(), + pull_number: number, + }), + context.octokit.rest.pulls.listCommits({ + ...context.repo(), + pull_number: number, + }), + ]); - app.log.info(context.payload); + // log raw to terminal + app.log.info({ diff, files, comments, reviewComments, reviews, commits }); - // Probot API note: context.repo() => { username: 'hiimbex', repo: 'testing-things' } - const res = await context.octokit.rest.repos.createDeployment( - context.repo({ - ref: context.payload.pull_request.head.ref, // The ref to deploy. This can be a branch, tag, or SHA. - task: "deploy", // Specifies a task to execute (e.g., deploy or deploy:migrations). - auto_merge: true, // Attempts to automatically merge the default branch into the requested ref, if it is behind the default branch. - required_contexts: [], // The status contexts to verify against commit status checks. If this parameter is omitted, then all unique contexts will be verified before a deployment is created. To bypass checking entirely pass an empty array. Defaults to all unique contexts. - payload: { - schema: "rocks!", - }, // JSON payload with extra information about the deployment. Default: "" - environment: "production", // Name for the target deployment environment (e.g., production, staging, qa) - description: "My Probot App's first deploy!", // Short description of the deployment - transient_environment: false, // Specifies if the given environment is specific to the deployment and will no longer exist at some point in the future. - production_environment: true, // Specifies if the given environment is one that end-users directly interact with. - }), - ); + // post summary back to the PR + const reply = ` +**Raw PR Data** - const deploymentId = res.data.id; - await context.octokit.rest.repos.createDeploymentStatus( - context.repo({ - deployment_id: deploymentId, - state: "success", // The state of the status. Can be one of error, failure, inactive, pending, or success - log_url: "https://example.com", // The log URL to associate with this status. This URL should contain output to keep the user updated while the task is running or serve as historical information for what happened in the deployment. - description: "My Probot App set a deployment status!", // A short description of the status. - environment_url: "https://example.com", // Sets the URL for accessing your environment. - auto_inactive: true, // Adds a new inactive status to all prior non-transient, non-production environment deployments with the same repository and environment name as the created status's deployment. An inactive status is only added to deployments that had a success state. - }), - ); - }, - ); +- Files changed: ${files.length} +- Commits: ${commits.length} +- Comments: ${comments.length} +- Review comments: ${reviewComments.length} +- Reviews: ${reviews.length} - // For more information on building apps: - // https://probot.github.io/docs/ +**Files** +${files.map(f => `- \`${f.filename}\` [${f.status}] +${f.additions} -${f.deletions}`).join("\n")} - // To get your app running against GitHub, see: - // https://probot.github.io/docs/development/ - }; +**Commits** +${commits.map(c => `- \`${c.sha.slice(0,7)}\` ${c.commit.message} (${c.commit.author.name})`).join("\n")} + +**Diff preview** +\`\`\`diff +${diff.slice(0, 1000)} +\`\`\` + `.trim(); + + await context.octokit.rest.issues.createComment( + context.issue({ body: reply }) + ); + + } catch (err) { + app.log.error(`Failed: ${err.message}`); + await context.octokit.rest.issues.createComment( + context.issue({ body: `Error: ${err.message}` }) + ); + } + }); +}; \ No newline at end of file diff --git a/memory/config/__pycache__/rules.cpython-311.pyc b/memory/config/__pycache__/rules.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1e326cea494e0d29b1d8356990f92460d6ddc7da GIT binary patch literal 855 zcmZWm&x_MQ6rO35G-*;Qi0pci-OH|}x(6?c+CpgyuI?6Tt*a#vVrN>jHffopx-A}Z z@KC|Sf`{#KPyPn|QyM9_1O&m8w?c1w@+Gyh+kIr-yzk4rFWYWrftXi-ZysV^|< zbeUEE?;K7C-h(yGLOMVWhoQvarFjR5UEvf6T@nz zBED3vZWSw9VYBQ@jOs*l=$@0pj0ZV8kY72zmA|Q0ZOUvFoa;>8?%GCO&9|EtrD{Fg zI!raG*=9Y!R>Nqj;q^8;JwM)RgJ=ku0&=15B`93rFW4irp!`XBCaYmmT^hH=pGwpzu>MgxX=!Qv3DcO&8z+cOF!h)IkqNWAI z)iht|=!{wplL9(wJ1jKfOS#HDExf(2l#35Hi#u~vd0cr=+@7QSR<2qo&d^815}?9h z_UKr=ZMF&g#w=R`7|hCk3uzL?7$2eK(dp-xa&A!AUw&PBQyMF)W99OP>z;DWl_ng< z+2Q#Kg6(j&h4qOfpv2PP?tae;`c_@(D0y+vA71g2>*M6QtIS~9ORkNRYiFRVxJqdH EFHEx0zW@LL literal 0 HcmV?d00001 diff --git a/memory/main.py b/memory/main.py index 5571580..089772c 100644 --- a/memory/main.py +++ b/memory/main.py @@ -1,44 +1,146 @@ import asyncio -from dotenv import load_dotenv -from config.rules import REVIEW_RULES, SYSTEM_RULES, CLAUDE_RULES, ASK_RULES import os import cognee +from dotenv import load_dotenv +from pydantic import BaseModel +from cognee.infrastructure.llm.LLMGateway import LLMGateway +from config.rules import SYSTEM_RULES, REVIEW_RULES, ASK_RULES + load_dotenv() -async def main(): - # Connect to Cognee Cloud @agentwaspai /deploy cognee-cloud + +# ── Response models ─────────────────────────────────────────────── + +class ReviewResponse(BaseModel): + review: str + +class AskResponse(BaseModel): + answer: str + + +# ── /review agent ───────────────────────────────────────────────── +# +# memory_query_fixed — always searches for review rules + past PR patterns +# memory_system_prompt — tells the model how to use the retrieved memory +# save_traces=True — saves every review as memory so future reviews +# learn from what the bot said before +# agent_session_name — stable name so Cognee tracks this agent's history +# across calls in Cognee Cloud Connections view + +@cognee.agent_memory( + agent_session_name="agentwaspai-review", + dataset_name="agent_memory_dataset", + memory_query_fixed="code review rules standards past PR issues bugs", + memory_system_prompt=REVIEW_RULES, + #with_memory=True, + #save_traces=True, + memory_top_k=5, +) +async def review_agent(pr_context: str) -> ReviewResponse: + """ + Decorator prepends relevant memory (rules + past reviews) to the LLM call. + LLMGateway picks it up automatically — no extra code needed here. + pr_context is the full structured PR document built by Probot. + """ + return await LLMGateway.acreate_structured_output( + text_input=pr_context, + system_prompt=REVIEW_RULES, + response_model=ReviewResponse, + ) + + +# ── /ask agent ──────────────────────────────────────────────────── +# +# memory_query_from_method="question" +# — uses the actual question as the Cognee search query +# — so memory retrieval changes per question, not fixed +# with_session_memory=True +# — also pulls in recent Q&A traces from this session +# — so the bot remembers what it said earlier in the same conversation + +@cognee.agent_memory( + agent_session_name="agentwaspai-ask", + dataset_name="agent_memory_dataset", + memory_query_from_method="question", + memory_system_prompt=ASK_RULES, + #with_memory=True, + #with_session_memory=True, # remember what was asked earlier in this PR session + #save_traces=True, + memory_top_k=5, +) +async def ask_agent(question: str, pr_context: str) -> AskResponse: + """ + memory_query_from_method="question" means Cognee searches memory + using the question itself as the query — so if the user asks about + async handling, Cognee returns everything it knows about async issues + in this codebase. + pr_context is passed separately as the current PR data. + """ + return await LLMGateway.acreate_structured_output( + text_input=f"Question: {question}\n\nPR Context:\n{pr_context}", + system_prompt=ASK_RULES, + response_model=AskResponse, + ) + + +# ── Main setup — run once at startup ───────────────────────────── + +async def setup(): + """ + Connect to Cognee Cloud and load rules into memory. + Call this once when the FastAPI service starts. + """ await cognee.serve( url=os.getenv("COGNEE_API_URL"), - api_key=os.getenv("COGNEE_API_KEY") + api_key=os.getenv("COGNEE_API_KEY"), ) + print("Connected to Cognee Cloud") - # Store content in memory — ingests, builds knowledge graph, enriches @agentwaspai /remember + # load rules into memory — these become the base knowledge + # the decorator retrieves from on every call await cognee.remember( - dataset_name="default_dataset", + data=SYSTEM_RULES, + dataset_name="agent_memory_dataset", ) + print("Rules loaded into memory") - #agent-memory @agentwaspai setup - await cognee.agent_memory( - dataset_name="default_dataset", - with_memory_query_fixed=True, - save_trace=True, - memory_query_fixed=CLAUDE_RULES, - memory_query_fixed_prompt=REVIEW_RULES, - ) - # Retrieve from memory @agentwaspai /review - results = await cognee.recall( - dataset_name="default_dataset", - query_text=ASK_RULES, - top_k=5 +# ── Test ────────────────────────────────────────────────────────── + +async def main(): + await setup() + + # test review agent + test_pr = """ + PR #5 — fix: add error handling to getUserById + Author: hiimbex + Files: src/db/users.js + + Diff: + - async function getUserById(id) { + - return await db.users.findUnique({ where: { id } }) + + async function getUserById(id) { + + try { + + return await db.users.findUnique({ where: { id } }) + + } catch (err) { + + throw new DatabaseError(err) + + } + """ + + print("\n--- Review Agent ---") + review = await review_agent(pr_context=test_pr) + print(review.review) + + print("\n--- Ask Agent ---") + answer = await ask_agent( + question="why does getUserById need error handling?", + pr_context=test_pr, ) + print(answer.answer) - print("Recall Results:") - for r in results: - print(r) - - # Disconnect when done await cognee.disconnect() -asyncio.run(main()) \ No newline at end of file + +if __name__ == "__main__": + asyncio.run(main()) \ No newline at end of file diff --git a/memory/rules/review.txt b/memory/rules/review.txt index e69de29..3e9285f 100644 --- a/memory/rules/review.txt +++ b/memory/rules/review.txt @@ -0,0 +1,38 @@ +# AgentWasp AI — Code Review Rules + +## Review Structure +Every review must follow this structure in this order: + +### Overview +Brief description of what this PR does and which files it touches. + +### Issues +List bugs, security risks, breaking changes, logic errors. +Each issue must include: +- What the problem is +- Why it is a problem +- How to fix it with a code example where relevant + +Use these severity prefixes: +- [Critical] — security risk, data loss, or breaking change +- [Major] — bug or logic error that affects functionality +- [Minor] — style or non-blocking improvement + +### Suggestions +Non-blocking improvements only. Mark each as non-blocking. + +### Positives +What was done well. One to three points maximum. + +### Summary +One paragraph verdict: Approved, Request Changes, or Needs Discussion. +If Request Changes, list exactly what must be resolved before merging. + +## Code Standards +- Flag hardcoded secrets or API keys immediately as Critical +- Check all async functions have proper error handling +- Flag missing input validation on functions accepting external data +- Flag N+1 query patterns in database or API calls +- Flag use of eval() or equivalent dangerous patterns +- Check new code is consistent with patterns already in the codebase +- Flag race conditions in concurrent or async code \ No newline at end of file diff --git a/test/index.test.js b/test/index.test.js index b75e8a4..80157ca 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -1,40 +1,15 @@ import nock from "nock"; -// Requiring our app implementation import myProbotApp from "../index.js"; import { Probot, ProbotOctokit } from "probot"; import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; -// Requiring our fixtures import payload from "./fixtures/pull_request.opened.json" with { type: "json" }; - import { describe, beforeEach, afterEach, test } from "node:test"; import assert from "node:assert"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const deployment = { - ref: "hiimbex-patch-1", - task: "deploy", - auto_merge: true, - required_contexts: [], - payload: { - schema: "rocks!", - }, - environment: "production", - description: "My Probot App's first deploy!", - transient_environment: false, - production_environment: true, -}; - -const deploymentStatus = { - state: "success", - log_url: "https://example.com", - description: "My Probot App set a deployment status!", - environment_url: "https://example.com", - auto_inactive: true, -}; - const privateKey = fs.readFileSync( path.join(__dirname, "fixtures/mock-cert.pem"), "utf-8", @@ -48,86 +23,80 @@ describe("My Probot app", () => { probot = new Probot({ appId: 123, privateKey, - // disable request throttling and retries for testing Octokit: ProbotOctokit.defaults((instanceOptions) => ({ ...instanceOptions, - retry: { enabled: false }, + retry: { enabled: false }, throttle: { enabled: false }, })), }); - // Load our app into probot probot.load(myProbotApp); }); - test("creates a deployment and a deployment status", async () => { + afterEach(() => { + nock.cleanAll(); + nock.enableNetConnect(); + }); + + + // ── Test 1 — PR opened, context fetched ────────────────────── + + test("fetches PR context when pull request is opened", async () => { const mock = nock("https://api.github.com") - // Test that we correctly return a test token .post("/app/installations/2/access_tokens") - .reply(200, { - token: "test", - permissions: { - deployments: "write", - pull_requests: "read", - }, - }) + .reply(200, { token: "test", permissions: { pull_requests: "read" } }) + + // diff + .get("/repos/hiimbex/testing-things/pulls/1") + .matchHeader("accept", "application/vnd.github.v3.diff") + .reply(200, "diff --git a/file.js b/file.js\n+new line") + + // files + .get("/repos/hiimbex/testing-things/pulls/1/files") + .reply(200, [{ filename: "file.js", status: "modified", additions: 1, deletions: 0, patch: "+new line" }]) + + // comments + .get("/repos/hiimbex/testing-things/issues/1/comments") + .reply(200, []) + + // review comments + .get("/repos/hiimbex/testing-things/pulls/1/comments") + .reply(200, []) + + // reviews + .get("/repos/hiimbex/testing-things/pulls/1/reviews") + .reply(200, []) + + // commits + .get("/repos/hiimbex/testing-things/pulls/1/commits") + .reply(200, [{ sha: "abc1234", commit: { message: "fix: test", author: { name: "hiimbex" } } }]); - // Test that a deployment is created - .post("/repos/hiimbex/testing-things/deployments", (body) => { - assert.deepStrictEqual(body, deployment); - return true; - }) - .reply(200, { id: 123 }) - - // Test that a deployment status is created - .post( - "/repos/hiimbex/testing-things/deployments/123/statuses", - (body) => { - assert.deepStrictEqual(body, deploymentStatus); - return true; - }, - ) - .reply(200); - - // Receive a webhook event await probot.receive({ name: "pull_request", payload }); assert.deepStrictEqual(mock.pendingMocks(), []); }); + + // ── Test 2 — mention in comment ────────────────────────────── + test("replies when the bot is mentioned in a comment", async () => { const issueCommentPayload = { action: "created", comment: { body: "hello @agentwaspai can you help?", + user: { type: "User" }, }, - issue: { - number: 1, - }, - repository: { - name: "testing-things", - owner: { - login: "hiimbex", - }, - }, - installation: { - id: 2, - }, + issue: { number: 1 }, + repository: { name: "testing-things", owner: { login: "hiimbex" } }, + installation: { id: 2 }, }; const mock = nock("https://api.github.com") .post("/app/installations/2/access_tokens") - .reply(200, { - token: "test", - permissions: { - deployments: "write", - pull_requests: "read", - issues: "write", - }, - }) + .reply(200, { token: "test", permissions: { issues: "write" } }) + .post("/repos/hiimbex/testing-things/issues/1/comments", (body) => { - assert.deepStrictEqual(body, { - body: "Hey! You mentioned me. Here's what I can do...", - }); + // just confirm a reply was posted — don't assert exact INTRO_MESSAGE text + assert.ok(body.body.length > 0); return true; }) .reply(200, { id: 456 }); @@ -136,15 +105,4 @@ describe("My Probot app", () => { assert.deepStrictEqual(mock.pendingMocks(), []); }); - - afterEach(() => { - nock.cleanAll(); - nock.enableNetConnect(); - }); -}); - -// For more information about testing with Jest see: -// https://facebook.github.io/jest/ - -// For more information about testing with Nock see: -// https://github.com/nock/nock +}); \ No newline at end of file From a7a05c544004fb32b72a51e11f301f2e1abf2d4f Mon Sep 17 00:00:00 2001 From: inline-arc Date: Thu, 9 Jul 2026 22:48:42 +0530 Subject: [PATCH 12/12] struture --- index.js | 222 ++++++++++++++------ memory/__pycache__/main.cpython-311.pyc | Bin 0 -> 5084 bytes memory/api/__pycache__/main.cpython-311.pyc | Bin 3609 -> 6432 bytes memory/api/main.py | 158 ++++++++++---- 4 files changed, 275 insertions(+), 105 deletions(-) create mode 100644 memory/__pycache__/main.cpython-311.pyc diff --git a/index.js b/index.js index 9ab104a..8fab967 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,21 @@ -const BOT_NAME = "@agentwaspai"; +import fetch from "node-fetch"; + +const BOT_NAME = "@agentwaspai"; +const COGNEE_API = process.env.COGNEE_SERVICE_URL || "http://localhost:8001"; + +function formatReviewReply(question, results) { + if (!results.length) { + return `No memory found for **${question}**.`; + } + + return [ + `# AgentWasp Recall`, + ``, + `**Question:** ${question}`, + ``, + ...results.map(r => r.text), + ].join("\n\n---\n\n"); +} export default (app) => { app.log.info("AgentWasp AI loaded."); @@ -6,65 +23,62 @@ export default (app) => { app.on("issue_comment.created", async (context) => { if (context.payload.comment.user?.type === "Bot") return; - const body = context.payload.comment.body.trim(); + const body = context.payload.comment.body.trim(); + const number = context.payload.issue.number; + const repo = context.payload.repository.full_name; if (!body.toLowerCase().includes(BOT_NAME.toLowerCase())) return; - if (!body.toLowerCase().includes("/review")) return; - if (!context.payload.issue.pull_request) { - await context.octokit.rest.issues.createComment( - context.issue({ body: "This command only works on pull requests." }) - ); - return; - } + // ── /review ─────────────────────────────────────────────── + if (body.toLowerCase().includes("/review")) { + if (!context.payload.issue.pull_request) { + await context.octokit.rest.issues.createComment( + context.issue({ body: "This command only works on pull requests." }) + ); + return; + } - const pr = context.payload.issue; - const number = pr.number; - - try { - // fetch everything in parallel - const [ - { data: diff }, - { data: files }, - { data: comments }, - { data: reviewComments }, - { data: reviews }, - { data: commits }, - ] = await Promise.all([ - context.octokit.rest.pulls.get({ - ...context.repo(), - pull_number: number, - mediaType: { format: "diff" }, - }), - context.octokit.rest.pulls.listFiles({ - ...context.repo(), - pull_number: number, - }), - context.octokit.rest.issues.listComments({ - ...context.repo(), - issue_number: number, - }), - context.octokit.rest.pulls.listReviewComments({ - ...context.repo(), - pull_number: number, - }), - context.octokit.rest.pulls.listReviews({ - ...context.repo(), - pull_number: number, - }), - context.octokit.rest.pulls.listCommits({ - ...context.repo(), - pull_number: number, - }), - ]); - - // log raw to terminal - app.log.info({ diff, files, comments, reviewComments, reviews, commits }); - - // post summary back to the PR - const reply = ` -**Raw PR Data** + try { + const [ + { data: diff }, + { data: files }, + { data: comments }, + { data: reviewComments }, + { data: reviews }, + { data: commits }, + ] = await Promise.all([ + context.octokit.rest.pulls.get({ + ...context.repo(), + pull_number: number, + mediaType: { format: "diff" }, + }), + context.octokit.rest.pulls.listFiles({ + ...context.repo(), + pull_number: number, + }), + context.octokit.rest.issues.listComments({ + ...context.repo(), + issue_number: number, + }), + context.octokit.rest.pulls.listReviewComments({ + ...context.repo(), + pull_number: number, + }), + context.octokit.rest.pulls.listReviews({ + ...context.repo(), + pull_number: number, + }), + context.octokit.rest.pulls.listCommits({ + ...context.repo(), + pull_number: number, + }), + ]); + app.log.info({ diff, files, comments, reviewComments, reviews, commits }); + + // post raw summary to PR + const summary = ` +**Raw PR Data** - Files changed: ${files.length} - Commits: ${commits.length} - Comments: ${comments.length} @@ -75,23 +89,101 @@ export default (app) => { ${files.map(f => `- \`${f.filename}\` [${f.status}] +${f.additions} -${f.deletions}`).join("\n")} **Commits** -${commits.map(c => `- \`${c.sha.slice(0,7)}\` ${c.commit.message} (${c.commit.author.name})`).join("\n")} +${commits.map(c => `- \`${c.sha.slice(0, 7)}\` ${c.commit.message} (${c.commit.author.name})`).join("\n")} **Diff preview** \`\`\`diff ${diff.slice(0, 1000)} \`\`\` - `.trim(); + `.trim(); + + await context.octokit.rest.issues.createComment( + context.issue({ body: summary }) + ); + + // ingest to Cognee + const ingestRes = await fetch(`${COGNEE_API}/ingest`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + repo, + pr_number: number, + diff, + files, + commits, + comments, + review_comments: reviewComments, + reviews, + }), + }); + + if (!ingestRes.ok) throw new Error(`Ingest failed: ${ingestRes.status}`); + + const ingestResult = await ingestRes.json(); + app.log.info(`Ingest response: ${JSON.stringify(ingestResult)}`); - await context.octokit.rest.issues.createComment( - context.issue({ body: reply }) - ); + await context.octokit.rest.issues.createComment( + context.issue({ + body: `PR #${number} data stored in memory and ready for review.`, + }) + ); - } catch (err) { - app.log.error(`Failed: ${err.message}`); - await context.octokit.rest.issues.createComment( - context.issue({ body: `Error: ${err.message}` }) - ); + } catch (err) { + app.log.error(`Review failed: ${err.message}`); + await context.octokit.rest.issues.createComment( + context.issue({ body: `Error: ${err.message}` }) + ); + } + return; + } + + // ── /ask ────────────────────────────────────────────────── + if (body.toLowerCase().includes("/ask")) { + const question = body + .replace(new RegExp(BOT_NAME, "gi"), "") + .replace(/\/ask/i, "") + .trim(); + + if (!question || question.length < 3) { + await context.octokit.rest.issues.createComment( + context.issue({ body: "Please provide a question after `/ask`." }) + ); + return; + } + + try { + const recallRes = await fetch(`${COGNEE_API}/recall`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + query: question, + repo, + pr_number: number, + }), + }); + + if (!recallRes.ok) throw new Error(`Recall failed: ${recallRes.status}`); + + const { results } = await recallRes.json(); + + const reply = results.length + ? `**AgentWasp AI — Memory Recall**\n\n${results.map((r) => { + // unescape \\n+ const text = r.text.replace(/\\n/g, "\n"); + return `---\n\n${text}`; + }).join("\n\n")}` + : "Nothing found in memory. Try `/review` first."; + + await context.octokit.rest.issues.createComment( + context.issue({ body: reply }) + ); + + } catch (err) { + app.log.error(`Ask failed: ${err.message}`); + await context.octokit.rest.issues.createComment( + context.issue({ body: `Error: ${err.message}` }) + ); + } + return; } }); }; \ No newline at end of file diff --git a/memory/__pycache__/main.cpython-311.pyc b/memory/__pycache__/main.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c2a6798031bb69a063cc22f3b44efcb2ff6bc00f GIT binary patch literal 5084 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 literal 3609 zcmcH*O>Y~=b!KH9-$#^;|F+%!QI6N+e>cOl8`(z9kXz6kfk2J0x<2u;2s2 zLS}GF1|Gb7yiURE0AA+-UYMO^(FaPh%k0?fZb-C#S??C?{3*PCW2adE17!<-o;FA% z*#pv%=5GtaV*8{|v%v$>NB*Pq&_3x=Hhe%jYWA|DcZ4SR5a>_N_9gp3Pkp!h8yjKA z*l{+(zPEj1OM!jE>o?G^d{eyfGv6R7PQ4BzpD#FuV-@l?{K`}L<(atTY14-7Onvl$ z7yilY>__k4OPh;01USKW4cok4U}n|}Oj~A_twi1{EaXjdAjtZPr(17FMbHfLt>I7Wh5!hq19zZnZ)F6WuI*o7BB-$y#x|W zyGi1@*L}mxnYmw>{D%2S(X^ehZ_sutv03w;GvX9RY^T7@5i36eqhNbaBrlY+@~Nzu zUvM&B2Q%$7x5VK**>*3h2j=@!rxoMShNy3!^)2> zF7r;D8bKId`z-+FmZugzvNpH==H|HjgR5@%S|xm~8opNUb?G&~R~iT(Xw36}(0%xZ znKrW7hUPhLb!A9szG$0FsNXbrIF}WBktR?Zgj2^ovTLY%0n)l^LwR%_%q^UUPsP!G6g0K z?(a`A_=3$bbUgLWUHtJ>ZaPJtPd9S|Fg198i}`K`kgJ4(A3H64rI54&%&xTOj%}%Z zwUTmQVq{hMm2#KfCHLf?k-Ic5&sZ;Ew-BctHspY!ok~Msdnz*>!^*}3p0)s(15u&+ zuIdGwseogu7rKfE_))}k?J13uHwll6iQPC%=`ovc^U1{=LXmuF{0I|^le;+W-*IGo|0!g)mO1674G;B-R{+% z>My~^KpORFqj&o%c@~VUUvTvSmktQ0T?r=mUe@!pMPoTzFj$*gqT`6ITn6NzTQXWA zx0`O-?kKHJ2n7R@usDD+Q+$ft$zKeT(yPKm|B~ z49sNyslL{t>mP1vk2)*b8&&NMmmbnrrnPHy&S6rJIZ~v?*2N_Bt?Mod`;3mJ9q!w z*PYSz>5b_>z4uuC~2qNL){ELzFg@Uul9`B zHAM;334rIaDl0Dufckf!iwqpE#^O7st6#!SF9~PFD*~45RBA=X?>nH46%8&v_*j#W z3TNZ8r(_DY<0*>;?w|!QR^z&#F;QcF+C<%m+$l;9AHiRwf%XOdI^uh}6*q$eR8bTK z^#YEK;jQK4A`~pRwQw0op6VT4W@-U?}a>8xz&q%-9-k*_hSv)oJx_el!k<;#By{;0itGwX$p4o}H>cxtBv8rAy z%XO8~>sT=kh3nRH!Fk!r*D0MB5{JR{4^U#BqC}Ig2Pjsh1F(lkfzM+1V{3yI8m-c3 zU7~al_u_-+GLd?lWE}?&+#gswTcQ0`3dK>PN9&{kaB!?i4mAeHD-@>atxFyBM|IKw zIJmD#9wtiX+RESML02BE(eCmwe4f$Ha_qrtYsn4kG5hL*OD8LIvPvfb@q1k($`DA1 GaQHWB1T~QW diff --git a/memory/api/main.py b/memory/api/main.py index 9acc525..62c6a9d 100644 --- a/memory/api/main.py +++ b/memory/api/main.py @@ -1,58 +1,136 @@ -from __future__ import annotations - -from typing import Any - +import os +import json import cognee +import logging + +from contextlib import asynccontextmanager from fastapi import FastAPI, HTTPException -from pydantic import BaseModel, Field +from pydantic import BaseModel +from dotenv import load_dotenv + +load_dotenv() + +logging.basicConfig(level=os.getenv("LOG_LEVEL", "INFO")) +logger = logging.getLogger(__name__) -app = FastAPI(title="Cognee Memory API", version="1.0.0") +@asynccontextmanager +async def lifespan(app: FastAPI): + logger.info("AgentWasp AI starting up") + yield + logger.info("AgentWasp AI shutting down") -class RememberRequest(BaseModel): - text: str = Field(..., min_length=1, description="Text to store in memory") +app = FastAPI(title="AgentWasp AI", lifespan=lifespan) + + +# ── Models ──────────────────────────────────────────────────────── + +class PRDataRequest(BaseModel): + repo: str + pr_number: int + diff: str + files: list + commits: list + comments: list + review_comments: list + reviews: list class RecallRequest(BaseModel): - query_text: str = Field(..., min_length=1, description="Text used to search memory") + query: str + repo: str = "" + pr_number: int = 0 + + +# ── Helper — connect to Cognee Cloud ───────────────────────────── + +async def connect(): + await cognee.serve( + url=os.getenv("COGNEE_API_URL"), + api_key=os.getenv("COGNEE_API_KEY"), + ) + logger.info("Connected to Cognee Cloud") + + +def serialize_pr_data(body: PRDataRequest) -> str: + payload = { + "repo": body.repo, + "pr_number": body.pr_number, + "diff": body.diff, + "files": body.files, + "commits": body.commits, + "comments": body.comments, + "review_comments": body.review_comments, + "reviews": body.reviews, + } + + return json.dumps(payload, separators=(",", ":"), ensure_ascii=False) + + +# ── Routes ──────────────────────────────────────────────────────── + +@app.get("/") +def root(): + return {"status": "ok", "service": "AgentWasp AI"} + + +@app.post("/ingest") +async def ingest(body: PRDataRequest): + try: + await connect() + raw_document = serialize_pr_data(body) -async def _call_cognee(method_name: str, *args: Any, **kwargs: Any) -> Any: - method = getattr(cognee, method_name) - try: - return await method(*args, **kwargs) - except Exception as exc: # pragma: no cover - surfaced through the API response - raise HTTPException(status_code=500, detail=str(exc)) from exc + logger.info(f"Ingesting PR #{body.pr_number} from {body.repo}") + logger.info(f"Document preview:\n{raw_document[:300]}") + await cognee.remember( + data=raw_document, + dataset_name=f"repo-{body.repo.replace('/', '-')}", + ) -@app.get("/health") -async def health() -> dict[str, str]: - return {"status": "ok"} + logger.info(f"PR #{body.pr_number} stored in Cognee Cloud") + return { + "status": "ok", + "pr_number": body.pr_number, + "repo": body.repo, + } -@app.post("/remember") -async def remember(payload: RememberRequest) -> dict[str, str]: - await _call_cognee("remember", payload.text) - return {"message": "saved"} + except Exception as e: + logger.error(f"Ingest failed: {e}") + raise HTTPException(status_code=500, detail=str(e)) @app.post("/recall") -async def recall(payload: RecallRequest) -> dict[str, Any]: - results = await _call_cognee("recall", query_text=payload.query_text) - items = [] - for result in results: - items.append( - { - "text": getattr(result, "text", str(result)), - "score": getattr(result, "score", None), - "metadata": getattr(result, "metadata", None), - } - ) - return {"query_text": payload.query_text, "results": items} - - -if __name__ == "__main__": - import uvicorn - - uvicorn.run(app, host="0.0.0.0", port=8000) +async def recall(body: RecallRequest): + try: + # connect every request + await connect() + + results = await cognee.recall( + query_text=body.query, + datasets=["repo-inline-arc-AgentwaspAi"] + ) + + context = [ + { + "text": getattr(r, "text", str(r)), + "dataset_name": getattr(r, "dataset_name", ""), + "source": getattr(r, "source", ""), + } + for r in results + ] + + logger.info(f"Recall returned {len(context)} results for: {body.query}") + + return { + "status": "ok", + "query": body.query, + "results": context, + } + + except Exception as e: + logger.error(f"Recall failed: {e}") + raise HTTPException(status_code=500, detail=str(e)) \ No newline at end of file