Skip to content

Commit 5e7274a

Browse files
nvidiaclaude
authored andcommitted
Add Cogito container, /lesson skill, CI/CD deploy pipeline
- cogito/: Content generation container (lesson watcher + paper/code enrichment via vLLM + x402 server). Fetches papers from arXiv, official code from GitHub, passes both to local LLM as context. - .claude/skills/lesson/: Claude Code skill to record design decisions as lesson_learned on AIN blockchain - .github/workflows/deploy-cogito.yml: CI/CD builds image, pushes to GHCR, registers deployment on AIN. Node pulls via passkey-bound GitHub identity (no Docker socket, no key exposure). - architecture.md: Full system design documentation - README.md: Updated with new architecture Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 7ee8e74 commit 5e7274a

22 files changed

Lines changed: 4647 additions & 52 deletions

.claude/skills/lesson/SKILL.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
---
2+
name: lesson
3+
description: Record a design decision as a lesson_learned on AIN blockchain. Use when the developer makes an architectural choice, picks a library, or resolves a trade-off.
4+
argument-hint: [decision description]
5+
allowed-tools: Bash(node *), Bash(npx *), Read, Grep
6+
---
7+
8+
Record the following design decision as a **lesson_learned** on the AIN blockchain knowledge graph.
9+
10+
**Decision:** $ARGUMENTS
11+
12+
## What to capture
13+
14+
Analyze the conversation context and extract:
15+
16+
1. **Title** — A concise name for this decision (e.g., "Event Sourcing over CRUD for Audit Trails")
17+
2. **Content** — The full context:
18+
- What was decided and why
19+
- What alternatives were considered and why they were rejected
20+
- What files/code are involved
21+
- Any relevant papers, articles, or documentation that informed the decision
22+
3. **Summary** — 1-2 sentence summary of the decision
23+
4. **Topic path** — Categorize under one of: `lessons/architecture`, `lessons/engineering`, `lessons/ai`, `lessons/blockchain`, `lessons/security`, or a custom `lessons/{category}`
24+
5. **Tags** — Relevant keywords (the system auto-adds `lesson_learned`)
25+
26+
## How to record
27+
28+
Run the record script to write to AIN blockchain:
29+
30+
```bash
31+
node /home/comcom/git/papers-with-claudecode/.claude/skills/lesson/scripts/record-lesson.js \
32+
--title "Your title here" \
33+
--content "Full decision context..." \
34+
--summary "Brief summary" \
35+
--topic "lessons/architecture" \
36+
--tags "tag1,tag2,tag3"
37+
```
38+
39+
If the script doesn't exist yet or fails, fall back to calling the Cogito container API:
40+
41+
```bash
42+
curl -X POST http://localhost:3402/lesson \
43+
-H "Content-Type: application/json" \
44+
-d '{
45+
"title": "Your title here",
46+
"content": "Full decision context...",
47+
"summary": "Brief summary",
48+
"topicPath": "lessons/architecture",
49+
"tags": ["tag1", "tag2"]
50+
}'
51+
```
52+
53+
## After recording
54+
55+
Confirm to the user:
56+
- What was recorded (title + summary)
57+
- The topic path it was filed under
58+
- That the Cogito container will automatically enrich it with related papers and their official code repositories
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
#!/usr/bin/env node
2+
/**
3+
* Record a lesson_learned to AIN blockchain.
4+
* Called by the /lesson Claude Code skill.
5+
*
6+
* Usage:
7+
* node record-lesson.js --title "..." --content "..." --summary "..." --topic "lessons/..." --tags "tag1,tag2"
8+
*
9+
* Or pipe JSON via stdin:
10+
* echo '{"title":"...","content":"..."}' | node record-lesson.js
11+
*
12+
* Falls back to POST http://localhost:3402/lesson if ain-js is not available.
13+
*/
14+
15+
const args = process.argv.slice(2);
16+
17+
function parseArgs(argv) {
18+
const result = {};
19+
for (let i = 0; i < argv.length; i++) {
20+
if (argv[i].startsWith('--') && i + 1 < argv.length) {
21+
const key = argv[i].slice(2);
22+
result[key] = argv[++i];
23+
}
24+
}
25+
return result;
26+
}
27+
28+
async function recordViaApi(lesson) {
29+
const url = process.env.COGITO_URL || 'http://localhost:3402';
30+
const res = await fetch(`${url}/lesson`, {
31+
method: 'POST',
32+
headers: { 'Content-Type': 'application/json' },
33+
body: JSON.stringify(lesson),
34+
});
35+
36+
if (!res.ok) {
37+
const text = await res.text();
38+
throw new Error(`API error ${res.status}: ${text}`);
39+
}
40+
41+
return res.json();
42+
}
43+
44+
async function recordViaAinJs(lesson) {
45+
// Try to use ain-js directly if available
46+
try {
47+
const AinModule = await import('@ainblockchain/ain-js');
48+
const Ain = AinModule.default || AinModule;
49+
50+
const providerUrl = process.env.AIN_PROVIDER_URL || 'https://devnet-api.ainetwork.ai';
51+
const privateKey = process.env.AIN_PRIVATE_KEY;
52+
53+
if (!privateKey) throw new Error('AIN_PRIVATE_KEY not set');
54+
55+
const ain = new Ain(providerUrl);
56+
ain.wallet.addAndSetDefaultAccount(privateKey);
57+
58+
const topicPath = lesson.topicPath || 'lessons';
59+
const tags = Array.isArray(lesson.tags) ? lesson.tags : (lesson.tags || '').split(',').filter(Boolean);
60+
61+
// Ensure topic exists
62+
try {
63+
const parts = topicPath.split('/');
64+
const title = parts[parts.length - 1].replace(/-/g, ' ');
65+
await ain.knowledge.registerTopic(topicPath, {
66+
title,
67+
description: `Lessons related to ${topicPath}`,
68+
});
69+
} catch {}
70+
71+
const result = await ain.knowledge.explore({
72+
topicPath,
73+
title: lesson.title,
74+
content: lesson.content,
75+
summary: lesson.summary || lesson.content.slice(0, 200),
76+
depth: 2,
77+
tags: ['lesson_learned', ...tags].join(','),
78+
});
79+
80+
return { success: true, entryId: result.entryId, method: 'ain-js' };
81+
} catch (err) {
82+
// ain-js not available or failed, will fall back to API
83+
throw err;
84+
}
85+
}
86+
87+
async function main() {
88+
let lesson;
89+
90+
if (args.length > 0) {
91+
const parsed = parseArgs(args);
92+
lesson = {
93+
title: parsed.title || 'Untitled Lesson',
94+
content: parsed.content || '',
95+
summary: parsed.summary || '',
96+
topicPath: parsed.topic || 'lessons',
97+
tags: parsed.tags ? parsed.tags.split(',') : [],
98+
};
99+
} else {
100+
// Read from stdin
101+
let input = '';
102+
for await (const chunk of process.stdin) input += chunk;
103+
lesson = JSON.parse(input);
104+
}
105+
106+
if (!lesson.title || !lesson.content) {
107+
console.error('Error: --title and --content are required');
108+
process.exit(1);
109+
}
110+
111+
console.log(`Recording lesson: "${lesson.title}"`);
112+
console.log(`Topic: ${lesson.topicPath || 'lessons'}`);
113+
114+
// Try ain-js first, fall back to API
115+
let result;
116+
try {
117+
result = await recordViaAinJs(lesson);
118+
} catch {
119+
try {
120+
result = await recordViaApi(lesson);
121+
result.method = 'api';
122+
} catch (apiErr) {
123+
console.error(`Failed to record lesson: ${apiErr.message}`);
124+
console.error('Make sure either AIN_PRIVATE_KEY is set or the Cogito container is running on port 3402');
125+
process.exit(1);
126+
}
127+
}
128+
129+
console.log(`Lesson recorded successfully!`);
130+
console.log(` Entry ID: ${result.entryId}`);
131+
console.log(` Method: ${result.method}`);
132+
console.log(` The Cogito container will enrich this with related papers + official code.`);
133+
}
134+
135+
main();
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
name: Build & Deploy Cogito
2+
3+
on:
4+
push:
5+
branches: [main]
6+
paths:
7+
- 'cogito/**'
8+
workflow_dispatch:
9+
10+
env:
11+
REGISTRY: ghcr.io
12+
IMAGE_NAME: ${{ github.repository_owner }}/cogito
13+
14+
jobs:
15+
build-and-push:
16+
runs-on: ubuntu-latest
17+
permissions:
18+
contents: read
19+
packages: write
20+
21+
steps:
22+
- uses: actions/checkout@v4
23+
24+
- name: Log in to GHCR
25+
uses: docker/login-action@v3
26+
with:
27+
registry: ghcr.io
28+
username: ${{ github.actor }}
29+
password: ${{ secrets.GITHUB_TOKEN }}
30+
31+
- name: Build and push Docker image
32+
uses: docker/build-push-action@v6
33+
with:
34+
context: cogito
35+
push: true
36+
tags: |
37+
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
38+
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
39+
40+
- name: Register deployment on AIN
41+
run: |
42+
cd cogito && npm ci
43+
node -e "
44+
const Ain = require('@ainblockchain/ain-js');
45+
const ain = new Ain('${{ secrets.AIN_PROVIDER_URL }}');
46+
ain.wallet.addAndSetDefaultAccount('${{ secrets.AIN_PRIVATE_KEY }}');
47+
const addr = ain.wallet.defaultAccount.address;
48+
ain.db.ref('/apps/knowledge/deployments/' + addr + '/cogito').setValue({
49+
value: {
50+
image: '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}',
51+
deployed_at: Date.now(),
52+
github_sha: '${{ github.sha }}',
53+
github_actor: '${{ github.actor }}',
54+
},
55+
nonce: -1,
56+
}).then(r => console.log('Registered:', JSON.stringify(r?.result)))
57+
.catch(e => console.log('Registration skipped:', e.message));
58+
"

0 commit comments

Comments
 (0)