Initial commit: LangChain evolution tutorial deck (132 slides)

- Cover, TOC, 5 dividers, 3 recap slides
- 5 sections (chains, langgraph, deepagents, openswe, ecosystem)
- design-system.js with theme tokens + 9 helper functions
- research/: timeline + sources + per-tech notes
- final-compile.js + merge.js for rebuild pipeline
- output/: langchain-evolution.pptx (2.3 MB) + langchain-evolution.pdf (1.1 MB) + 7 sample previews
This commit is contained in:
2026-06-22 11:29:03 +03:00
parent ed5b5c91bf
commit 75601988c2
220 changed files with 13069 additions and 3 deletions
+28
View File
@@ -0,0 +1,28 @@
/**
* slides/01-cover.js
* ----------------------------------------------------------------------------
* Slide 01 -- Cover
* Section divider style with stage number, title, intro paragraph.
*/
'use strict';
const { helpers } = require('../../design-system');
function buildCover(pres, theme) {
const slide = pres.addSlide();
helpers.addSectionDivider(slide, pres, theme, {
number: '4',
eyebrow: 'STAGE 4',
title: 'Open SWE: async coding agent',
intro:
'Open-source фреймворк LangChain Inc. для построения внутренних ' +
'кодинг-агентов организации. Reference architecture поверх Deep Agents, ' +
'pluggable sandboxes, триггеры из Slack/Linear/GitHub, draft PR ' +
'автоматически. Воспроизводит паттерны Stripe Minions, Ramp Inspect, ' +
'Coinbase Cloudbot -- но с открытым исходным кодом.',
});
helpers.addPageNumber(slide, pres, theme, 1);
return slide;
}
module.exports = { buildCover };
@@ -0,0 +1,71 @@
/**
* slides/02-what-is-openswe.js
* ----------------------------------------------------------------------------
* Slide 02 -- What is Open SWE: positioning
* One big picture slide with positioning callout.
*/
'use strict';
const { helpers, layouts } = require('../../design-system');
function buildWhatIsOpenSWE(pres, theme) {
const slide = pres.addSlide();
helpers.slideBase(slide, pres, theme);
helpers.addHeader(slide, pres, theme, {
eyebrow: 'STAGE 4',
sectionNumber: 4,
title: 'Open SWE: позиционирование',
});
// Left column: intro callout
helpers.addCallout(slide, pres, theme, {
x: 0.5, y: layouts.CONTENT_TOP, w: 4.5, h: 1.55,
kind: 'info',
title: 'Не готовый продукт',
text:
'Стартовый шаблон, не ' +
'SaaS. Ops-работа: sandbox, ' +
'модель, триггеры, промпты.',
});
helpers.addCallout(slide, pres, theme, {
x: 0.5, y: 3.1, w: 4.5, h: 1.75,
kind: 'success',
title: 'Colleague, not copilot',
text:
'Внутренний кодинг-агент, ' +
'не IDE-assistant. Slack / ' +
'Linear / GitHub -> draft PR ' +
'с тестами.',
});
// Right column: key facts
helpers.addCodeBlock(slide, pres, theme, {
x: 5.3, y: layouts.CONTENT_TOP, w: 4.2, h: 3.4,
language: 'python',
code: [
'# github.com/langchain-ai/open-swe',
'',
'stars: ~10k',
'commits: 971+',
'license: MIT',
'language: Python + TypeScript',
'announce: 08.2025',
'rewrite: 03.2026',
'',
'# blog.langchain.com/open-swe',
'# INSTALLATION.md',
'# CUSTOMIZATION.md',
].join('\n'),
filePath: 'meta: open-swe repo',
startLine: 1,
});
helpers.addPageNumber(slide, pres, theme, 2);
helpers.addSourceLine(slide, pres, theme, {
source: 'github.com/langchain-ai/open-swe (README + INSTALLATION.md)',
});
return slide;
}
module.exports = { buildWhatIsOpenSWE };
@@ -0,0 +1,101 @@
/**
* slides/03-architecture-overview.js
* ----------------------------------------------------------------------------
* Slide 03 -- Architecture overview: layered model
* Diagram + import surface code.
*/
'use strict';
const { helpers, layouts } = require('../../design-system');
function buildArchitectureOverview(pres, theme) {
const slide = pres.addSlide();
helpers.slideBase(slide, pres, theme);
helpers.addHeader(slide, pres, theme, {
eyebrow: 'STAGE 4',
sectionNumber: 4,
title: 'Архитектура: 7 слоев',
});
// Left: architecture diagram
const diagX = 0.5;
const diagY = layouts.CONTENT_TOP;
const diagW = 4.4;
const diagH = 3.5;
slide.addShape(pres.ShapeType.roundRect, {
x: diagX, y: diagY, w: diagW, h: diagH,
fill: { color: theme.palette.bg.elevated },
line: { color: theme.palette.border.subtle, width: 1 },
rectRadius: 0.08,
});
// 7 stacked layers
const layers = [
['Triggers', 'Slack / Linear / GitHub / Web UI'],
['Validation', 'prompt + middleware (HITL, approval)'],
['Orchestration','subagents + middleware'],
['Context', 'AGENTS.md из репозитория'],
['Tools', 'execute, fetch_url, linear_comment, slack_thread_reply'],
['Sandbox', 'Modal / Daytona / Runloop / LangSmith'],
['Harness', 'create_deep_agent (Deep Agents)'],
];
const layerH = (diagH - 0.3) / layers.length;
layers.forEach(function (layer, idx) {
const y = diagY + 0.15 + idx * layerH;
slide.addShape(pres.ShapeType.rect, {
x: diagX + 0.15, y: y, w: diagW - 0.3, h: layerH - 0.05,
fill: { color: idx === layers.length - 1
? theme.palette.accent.primary
: theme.palette.bg.code },
line: { color: theme.palette.border.subtle, width: 0.5 },
});
slide.addText(layer[0], {
x: diagX + 0.25, y: y + 0.02, w: diagW - 0.5, h: 0.22,
fontFace: helpers.withFallback(theme.fonts.ui),
fontSize: theme.sizes.body,
color: idx === layers.length - 1
? theme.palette.text.inverse
: theme.palette.text.primary,
bold: true,
});
slide.addText(layer[1], {
x: diagX + 0.25, y: y + 0.22, w: diagW - 0.5, h: layerH - 0.27,
fontFace: helpers.withFallback(theme.fonts.code),
fontSize: theme.sizes.caption,
color: idx === layers.length - 1
? theme.palette.text.inverse
: theme.palette.text.secondary,
});
});
// Right: import surface
helpers.addCodeBlock(slide, pres, theme, {
x: 5.2, y: layouts.CONTENT_TOP, w: 4.3, h: 3.5,
language: 'python',
code: [
'from open_swe.agent import create_agent',
'from open_swe.middleware import (',
' check_message_queue_before_model,',
' notify_step_limit_reached,',
' open_pr_if_needed,',
' ToolErrorMiddleware,',
')',
'from open_swe.sandbox import (',
' SandboxBackend,',
' ModalBackend, DaytonaBackend,',
')',
].join('\n'),
filePath: 'open_swe/__init__.py',
startLine: 1,
});
helpers.addPageNumber(slide, pres, theme, 3);
helpers.addSourceLine(slide, pres, theme, {
source: 'research/per-tech/openswe.md: 26-50',
});
return slide;
}
module.exports = { buildArchitectureOverview };
@@ -0,0 +1,70 @@
/**
* slides/04-create-deep-agent.js
* ----------------------------------------------------------------------------
* Slide 04 -- create_deep_agent: composition point
* Core entry point with sandbox + middleware.
*/
'use strict';
const { helpers, layouts } = require('../../design-system');
function buildCreateDeepAgent(pres, theme) {
const slide = pres.addSlide();
helpers.slideBase(slide, pres, theme);
helpers.addHeader(slide, pres, theme, {
eyebrow: 'STAGE 4',
sectionNumber: 4,
title: 'create_deep_agent + backend',
});
helpers.addCodeBlock(slide, pres, theme, {
x: 0.5, y: layouts.CONTENT_TOP, w: 6.0, h: 3.5,
language: 'python',
code: [
'from deepagents import create_deep_agent',
'from open_swe.sandbox import DaytonaBackend',
'',
'agent = create_deep_agent(',
' model="anthropic:claude-opus-4-6",',
' tools=[execute, fetch_url,',
' linear_comment,',
' slack_thread_reply],',
' backend=DaytonaBackend(api_key="..."),',
' middleware=[open_pr_if_needed],',
')',
].join('\n'),
filePath: 'examples/minimal_agent.py',
startLine: 1,
highlightLines: [4, 8, 9],
});
helpers.addCallout(slide, pres, theme, {
x: 6.8, y: layouts.CONTENT_TOP, w: 2.7, h: 1.4,
kind: 'info',
title: 'Один harness',
text:
'Март 2026: multi-agent ' +
'(Manager/Planner/Programmer/' +
'Reviewer) заменили на единый ' +
'deep-agent harness.',
});
helpers.addCallout(slide, pres, theme, {
x: 6.8, y: 2.9, w: 2.7, h: 1.55,
kind: 'success',
title: 'Upgrade path',
text:
'Подтягиваешь улучшения Deep ' +
'Agents бесплатно. Subagents ' +
'изолируют контекст, middleware ' +
'дают orchestration.',
});
helpers.addPageNumber(slide, pres, theme, 4);
helpers.addSourceLine(slide, pres, theme, {
source: 'research/per-tech/openswe.md: 52-77',
});
return slide;
}
module.exports = { buildCreateDeepAgent };
@@ -0,0 +1,79 @@
/**
* slides/05-agents-md-context.js
* ----------------------------------------------------------------------------
* Slide 05 -- AGENTS.md convention
* Context injection pattern.
*/
'use strict';
const { helpers, layouts } = require('../../design-system');
function buildAgentsMdConvention(pres, theme) {
const slide = pres.addSlide();
helpers.slideBase(slide, pres, theme);
helpers.addHeader(slide, pres, theme, {
eyebrow: 'STAGE 4',
sectionNumber: 4,
title: 'AGENTS.md как system prompt',
});
helpers.addCodeBlock(slide, pres, theme, {
x: 0.5, y: layouts.CONTENT_TOP, w: 5.6, h: 1.7,
language: 'python',
code: [
'from pathlib import Path',
'',
'def construct_system_prompt(',
' repo_dir, base_prompt',
'):',
' agents_md = Path(repo_dir) / "AGENTS.md"',
' extra = agents_md.read_text() if',
' agents_md.exists() else ""',
' return base_prompt + extra',
].join('\n'),
filePath: 'open_swe/prompts.py',
startLine: 1,
});
helpers.addCodeBlock(slide, pres, theme, {
x: 0.5, y: 3.3, w: 5.6, h: 1.55,
language: 'markdown',
code: [
'# AGENTS.md (repo root)',
'',
'- use uv, not pip',
'- run pytest before commit',
'- never push to main directly',
].join('\n'),
filePath: 'AGENTS.md',
startLine: 1,
});
helpers.addCallout(slide, pres, theme, {
x: 6.3, y: layouts.CONTENT_TOP, w: 3.2, h: 1.7,
kind: 'info',
title: 'Convention',
text:
'Организационный паттерн. ' +
'Тот же файл читают Cursor, ' +
'Aider, Devin.',
});
helpers.addCallout(slide, pres, theme, {
x: 6.3, y: 3.3, w: 3.2, h: 1.55,
kind: 'warning',
title: 'Подводный камень',
text:
'Open SWE доверяет AGENTS.md. ' +
'Вредоносный блок попадает ' +
'в system prompt.',
});
helpers.addPageNumber(slide, pres, theme, 5);
helpers.addSourceLine(slide, pres, theme, {
source: 'research/per-tech/openswe.md: 113-115, 200-209',
});
return slide;
}
module.exports = { buildAgentsMdConvention };
+71
View File
@@ -0,0 +1,71 @@
/**
* slides/06-middleware.js
* ----------------------------------------------------------------------------
* Slide 06 -- Middleware: AgentMiddleware extension
* Custom middleware pattern.
*/
'use strict';
const { helpers, layouts } = require('../../design-system');
function buildMiddleware(pres, theme) {
const slide = pres.addSlide();
helpers.slideBase(slide, pres, theme);
helpers.addHeader(slide, pres, theme, {
eyebrow: 'STAGE 4',
sectionNumber: 4,
title: 'Middleware: точки расширения',
});
helpers.addCodeBlock(slide, pres, theme, {
x: 0.5, y: layouts.CONTENT_TOP, w: 5.6, h: 3.5,
language: 'python',
code: [
'from langchain.agents.middleware import (',
' AgentMiddleware,',
')',
'',
'class AuditMiddleware(AgentMiddleware):',
' def after_model(self, state, runtime):',
' runtime.logger.info(',
' f"step={state.get(\"step\")}"',
' )',
' return state',
'',
' def before_model(self, state, runtime):',
' return state # inject reminder',
].join('\n'),
filePath: 'examples/audit_middleware.py',
startLine: 1,
highlightLines: [5, 6, 7, 8],
});
helpers.addCallout(slide, pres, theme, {
x: 6.3, y: layouts.CONTENT_TOP, w: 3.2, h: 1.7,
kind: 'info',
title: 'Built-in middleware',
text:
'* check_message_queue\n' +
'* notify_step_limit\n' +
'* open_pr_if_needed\n' +
'* ToolErrorMiddleware',
});
helpers.addCallout(slide, pres, theme, {
x: 6.3, y: 3.0, w: 3.2, h: 1.8,
kind: 'success',
title: 'Типичные кастомы',
text:
'Approval gate перед PR, cost ' +
'guard на длину контекста, ' +
'redaction секретов в логах.',
});
helpers.addPageNumber(slide, pres, theme, 6);
helpers.addSourceLine(slide, pres, theme, {
source: 'research/per-tech/openswe.md: 117-136, 213-223',
});
return slide;
}
module.exports = { buildMiddleware };
@@ -0,0 +1,99 @@
/**
* slides/07-sandbox-providers.js
* ----------------------------------------------------------------------------
* Slide 07 -- Sandbox providers: comparison
* Table of Modal / Daytona / Runloop / LangSmith.
*/
'use strict';
const { helpers, layouts } = require('../../design-system');
function buildSandboxProviders(pres, theme) {
const slide = pres.addSlide();
helpers.slideBase(slide, pres, theme);
helpers.addHeader(slide, pres, theme, {
eyebrow: 'STAGE 4',
sectionNumber: 4,
title: 'Sandbox-провайдеры',
});
// Comparison table card
const x = 0.5;
const y = layouts.CONTENT_TOP;
const w = 9.0;
const h = 3.55;
slide.addShape(pres.ShapeType.roundRect, {
x: x, y: y, w: w, h: h,
fill: { color: theme.palette.bg.elevated },
line: { color: theme.palette.border.subtle, width: 1 },
rectRadius: 0.08,
});
// Table headers
const cols = [
{ label: 'Provider', w: 1.6 },
{ label: 'Setup', w: 1.9 },
{ label: 'Pricing model', w: 1.7 },
{ label: 'Persistent state', w: 1.6 },
{ label: 'Best for', w: 2.2 },
];
const headerY = y + 0.1;
let cx = x + 0.15;
cols.forEach(function (c) {
slide.addShape(pres.ShapeType.rect, {
x: cx, y: headerY, w: c.w, h: 0.32,
fill: { color: theme.palette.bg.code },
line: { color: theme.palette.border.subtle, width: 0.5 },
});
slide.addText(c.label, {
x: cx + 0.05, y: headerY, w: c.w - 0.1, h: 0.32,
fontFace: helpers.withFallback(theme.fonts.ui),
fontSize: theme.sizes.caption,
color: theme.palette.accent.tertiary,
bold: true,
charSpacing: 2,
valign: 'middle',
});
cx += c.w;
});
const rows = [
['ModalBackend', 'token_id + token_secret', 'per-second + GB-s', 'yes (volume)', 'Python-first, GPU-доступный'],
['DaytonaBackend','api_key', 'per-session', 'yes (volume)', 'default в README'],
['RunloopBackend','api_key', 'per-second', 'yes (volume)', 'dev-цикл, snapshot/restart'],
['LangSmithBackend','LANGSMITH_API_KEY', 'через LangSmith', 'через LS', 'уже платите за LangSmith'],
];
const rowH = 0.7;
rows.forEach(function (row, idx) {
const ry = headerY + 0.32 + idx * rowH;
cx = x + 0.15;
row.forEach(function (cell, cidx) {
slide.addShape(pres.ShapeType.rect, {
x: cx, y: ry, w: cols[cidx].w, h: rowH,
fill: { color: idx % 2 === 0
? theme.palette.bg.primary
: theme.palette.bg.overlay },
line: { color: theme.palette.border.subtle, width: 0.4 },
});
slide.addText(cell, {
x: cx + 0.05, y: ry, w: cols[cidx].w - 0.1, h: rowH,
fontFace: helpers.withFallback(theme.fonts.code),
fontSize: 9,
color: theme.palette.text.primary,
valign: 'middle',
});
cx += cols[cidx].w;
});
});
helpers.addPageNumber(slide, pres, theme, 7);
helpers.addSourceLine(slide, pres, theme, {
source: 'research/per-tech/openswe.md: 78-89, 139-145',
});
return slide;
}
module.exports = { buildSandboxProviders };
@@ -0,0 +1,94 @@
/**
* slides/08-sandbox-imports.js
* ----------------------------------------------------------------------------
* Slide 08 -- Sandbox imports + custom backend stub
*/
'use strict';
const { helpers, layouts } = require('../../design-system');
function buildSandboxImports(pres, theme) {
const slide = pres.addSlide();
helpers.slideBase(slide, pres, theme);
helpers.addHeader(slide, pres, theme, {
eyebrow: 'STAGE 4',
sectionNumber: 4,
title: 'Sandbox: imports + custom',
});
helpers.addCodeBlock(slide, pres, theme, {
x: 0.5, y: layouts.CONTENT_TOP, w: 5.0, h: 3.4,
language: 'python',
code: [
'from open_swe.sandbox import (',
' ModalBackend,',
' DaytonaBackend,',
' RunloopBackend,',
' LangSmithBackend,',
')',
'',
'# Modal (Python-first, GPU)',
'backend = ModalBackend(',
' token_id="..."',
' token_secret="..."',
')',
].join('\n'),
filePath: 'open_swe/sandbox/__init__.py',
startLine: 1,
});
helpers.addCodeBlock(slide, pres, theme, {
x: 5.7, y: layouts.CONTENT_TOP, w: 3.8, h: 3.4,
language: 'python',
code: [
'# свой backend: devbox-пул',
'from open_swe.sandbox import (',
' SandboxBackend,',
')',
'',
'class MyInternalBackend(SandboxBackend):',
' def execute(self, cmd):',
' return self._run(cmd)',
'',
' def read_file(self, p):',
' return self._fetch(p)',
].join('\n'),
filePath: 'examples/my_internal_backend.py',
startLine: 1,
highlightLines: [5, 8, 9, 11, 12, 13, 14, 15],
});
helpers.addCodeBlock(slide, pres, theme, {
x: 5.7, y: layouts.CONTENT_TOP, w: 3.8, h: 3.4,
language: 'python',
code: [
'# свой backend: devbox-пул',
'from open_swe.sandbox import (',
' SandboxBackend,',
')',
'',
'class MyInternalBackend(',
' SandboxBackend',
'):',
' def __init__(self, conn):',
' self.conn = conn',
'',
' def execute(self, cmd):',
' return self._run(cmd)',
'',
' def read_file(self, p):',
' return self._fetch(p)',
].join('\n'),
filePath: 'examples/my_internal_backend.py',
startLine: 1,
highlightLines: [11, 12, 13, 14, 15, 16, 19, 20, 21, 22],
});
helpers.addPageNumber(slide, pres, theme, 8);
helpers.addSourceLine(slide, pres, theme, {
source: 'research/per-tech/openswe.md: 80-86, 250-264',
});
return slide;
}
module.exports = { buildSandboxImports };
@@ -0,0 +1,78 @@
/**
* slides/09-installation-1.js
* ----------------------------------------------------------------------------
* Slide 09 -- Installation: prerequisites + clone
* Steps 1-2 of 5.
*/
'use strict';
const { helpers, layouts } = require('../../design-system');
function buildInstallPart1(pres, theme) {
const slide = pres.addSlide();
helpers.slideBase(slide, pres, theme);
helpers.addHeader(slide, pres, theme, {
eyebrow: 'STAGE 4',
sectionNumber: 4,
title: 'Установка (1/2): шаги 1-2',
});
helpers.addCodeBlock(slide, pres, theme, {
x: 0.5, y: layouts.CONTENT_TOP, w: 5.5, h: 1.5,
language: 'bash',
code: [
'# 1. prerequisites',
'python --version # >= 3.11',
'node --version # >= 20',
'uv --version # или pip',
'docker --version # для dev',
].join('\n'),
filePath: 'INSTALLATION.md: step 1',
startLine: 1,
});
helpers.addCodeBlock(slide, pres, theme, {
x: 0.5, y: 3.1, w: 5.5, h: 1.75,
language: 'bash',
code: [
'# 2. clone + install',
'git clone https://github.com/',
' langchain-ai/open-swe.git',
'cd open-swe',
'uv sync # или pip install -e .',
].join('\n'),
filePath: 'INSTALLATION.md: step 2',
startLine: 1,
});
helpers.addCallout(slide, pres, theme, {
x: 6.2, y: layouts.CONTENT_TOP, w: 3.3, h: 1.55,
kind: 'info',
title: 'Не SaaS',
text:
'Open SWE -- не готовый сервис. ' +
'После клонирования нужно ' +
'поднять backend, UI, sandbox, ' +
'GitHub App, LangSmith.',
});
helpers.addCallout(slide, pres, theme, {
x: 6.2, y: 3.1, w: 3.3, h: 1.75,
kind: 'warning',
title: 'ENV-файл',
text:
'cp .env.example .env, затем ' +
'заполните:\n' +
'- GITHUB_APP_ID\n' +
'- LANGSMITH_API_KEY\n' +
'- DAYTONA_API_KEY',
});
helpers.addPageNumber(slide, pres, theme, 9);
helpers.addSourceLine(slide, pres, theme, {
source: 'github.com/langchain-ai/open-swe/blob/main/INSTALLATION.md',
});
return slide;
}
module.exports = { buildInstallPart1 };
@@ -0,0 +1,70 @@
/**
* slides/10-installation-2.js
* ----------------------------------------------------------------------------
* Slide 10 -- Installation: services up + UI
* Steps 3-5 of 5.
*/
'use strict';
const { helpers, layouts } = require('../../design-system');
function buildInstallPart2(pres, theme) {
const slide = pres.addSlide();
helpers.slideBase(slide, pres, theme);
helpers.addHeader(slide, pres, theme, {
eyebrow: 'STAGE 4',
sectionNumber: 4,
title: 'Установка (2/2): шаги 3-5',
});
helpers.addCodeBlock(slide, pres, theme, {
x: 0.5, y: layouts.CONTENT_TOP, w: 6.0, h: 3.5,
language: 'bash',
code: [
'# 3. backend (FastAPI)',
'uv run apps/open-swe/main.py',
'# -> :8000',
'',
'# 4. UI (TanStack Start + Vite)',
'cd apps/open-swe-ui',
'pnpm install && pnpm dev',
'# -> :3000',
'',
'# 5. smoke-test',
'curl -X POST :8000/webhooks/slack \\',
' -d \'{"text":"@open-swe hello"}\'',
'# -> {"thread_id": "..."}',
].join('\n'),
filePath: 'INSTALLATION.md: steps 3-5',
startLine: 1,
});
helpers.addCallout(slide, pres, theme, {
x: 6.7, y: layouts.CONTENT_TOP, w: 2.8, h: 1.7,
kind: 'success',
title: 'Готово',
text:
'Backend :8000\n' +
'UI :3000\n' +
'Webhook :8000/webhooks/*\n\n' +
'Можно слать @open-swe.',
});
helpers.addCallout(slide, pres, theme, {
x: 6.7, y: 3.0, w: 2.8, h: 1.85,
kind: 'info',
title: 'Production',
text:
'Локальный запуск -- только dev. ' +
'Для prod: docker compose, k8s, ' +
'managed Postgres, Vault, TLS.',
});
helpers.addPageNumber(slide, pres, theme, 10);
helpers.addSourceLine(slide, pres, theme, {
source: 'INSTALLATION.md + INSTALLATION in repo root',
});
return slide;
}
module.exports = { buildInstallPart2 };
+72
View File
@@ -0,0 +1,72 @@
/**
* slides/11-github-app.js
* ----------------------------------------------------------------------------
* Slide 11 -- GitHub App setup
* Manifest + permissions.
*/
'use strict';
const { helpers, layouts } = require('../../design-system');
function buildGithubApp(pres, theme) {
const slide = pres.addSlide();
helpers.slideBase(slide, pres, theme);
helpers.addHeader(slide, pres, theme, {
eyebrow: 'STAGE 4',
sectionNumber: 4,
title: 'GitHub App: manifest',
});
helpers.addCodeBlock(slide, pres, theme, {
x: 0.5, y: layouts.CONTENT_TOP, w: 5.7, h: 3.5,
language: 'yaml',
code: [
'# github-app-manifest.yaml',
'name: open-swe-internal',
'url: https://open-swe.example.com',
'hook_attributes:',
' url: https://open-swe.example.com/',
' webhooks/github',
' events:',
' - issue_comment',
' - pull_request',
' - pull_request_review',
'default_permissions:',
' contents: write',
' pull_requests: write',
].join('\n'),
filePath: 'config/github-app-manifest.yaml',
startLine: 1,
highlightLines: [3, 4, 5, 6, 7, 8, 9],
});
helpers.addCallout(slide, pres, theme, {
x: 6.4, y: layouts.CONTENT_TOP, w: 3.1, h: 1.5,
kind: 'info',
title: 'Создание App',
text:
'1. github.com/settings/apps/new\n' +
'2. вставить manifest\n' +
'3. запомнить App ID\n' +
'4. скачать private key',
});
helpers.addCallout(slide, pres, theme, {
x: 6.4, y: 2.85, w: 3.1, h: 2.0,
kind: 'warning',
title: 'Permissions',
text:
'contents:write -- ветки\n' +
'pull_requests:write -- draft PR\n' +
'issues:write -- комментарии\n' +
'metadata:read -- repo info',
});
helpers.addPageNumber(slide, pres, theme, 11);
helpers.addSourceLine(slide, pres, theme, {
source: 'docs.github.com/apps/creating-github-apps',
});
return slide;
}
module.exports = { buildGithubApp };
+68
View File
@@ -0,0 +1,68 @@
/**
* slides/12-langsmith.js
* ----------------------------------------------------------------------------
* Slide 12 -- LangSmith setup + API keys + snapshot
* Account creation and runtime config.
*/
'use strict';
const { helpers, layouts } = require('../../design-system');
function buildLangSmithSetup(pres, theme) {
const slide = pres.addSlide();
helpers.slideBase(slide, pres, theme);
helpers.addHeader(slide, pres, theme, {
eyebrow: 'STAGE 4',
sectionNumber: 4,
title: 'LangSmith: setup',
});
helpers.addCodeBlock(slide, pres, theme, {
x: 0.5, y: layouts.CONTENT_TOP, w: 5.6, h: 3.5,
language: 'bash',
code: [
'# .env (НЕ коммитить)',
'LANGSMITH_TRACING=true',
'LANGSMITH_ENDPOINT=https://',
' api.smith.langchain.com',
'LANGSMITH_API_KEY=lsv2_...',
'LANGSMITH_PROJECT=open-swe-prod',
'LANGSMITH_SNAPSHOT=true',
'',
'# для sandbox-прокси:',
'LANGSMITH_SANDBOX_BACKEND=true',
].join('\n'),
filePath: '.env',
startLine: 1,
});
helpers.addCallout(slide, pres, theme, {
x: 6.3, y: layouts.CONTENT_TOP, w: 3.2, h: 1.5,
kind: 'info',
title: 'Зачем snapshot',
text:
'Каждый thread = checkpoint. ' +
'Follow-up из всех каналов ' +
'подхватывают состояние ' +
'по thread_id.',
});
helpers.addCallout(slide, pres, theme, {
x: 6.3, y: 2.85, w: 3.2, h: 2.0,
kind: 'success',
title: 'API keys',
text:
'* Personal -- smith.langchain.com\n' +
'* Org-level -- shared tracing\n' +
'* Service -- для CI / batch\n' +
'Rotate каждые 90 дней.',
});
helpers.addPageNumber(slide, pres, theme, 12);
helpers.addSourceLine(slide, pres, theme, {
source: 'docs.smith.langchain.com',
});
return slide;
}
module.exports = { buildLangSmithSetup };
@@ -0,0 +1,119 @@
/**
* slides/13-triggers-overview.js
* ----------------------------------------------------------------------------
* Slide 13 -- Triggers: 3 surface overview
* Slack / Linear / GitHub with @open-swe pattern.
*/
'use strict';
const { helpers, layouts } = require('../../design-system');
function buildTriggersOverview(pres, theme) {
const slide = pres.addSlide();
helpers.slideBase(slide, pres, theme);
helpers.addHeader(slide, pres, theme, {
eyebrow: 'STAGE 4',
sectionNumber: 4,
title: 'Triggers overview',
});
// 3 trigger cards
const cardY = layouts.CONTENT_TOP;
const cardH = 3.5;
const cardW = 2.95;
const gap = 0.13;
const triggers = [
{
name: 'Slack',
icon: '@',
desc: 'В любом thread канала. Поддерживает синтаксис repo:owner/name.',
syntax: '@open-swe fix the auth bug\nrepo:acme/api',
color: theme.palette.accent.primary,
},
{
name: 'Linear',
icon: '#',
desc: 'В комментарии к issue. Привязывает thread к Linear-тикету.',
syntax: '@openswe implement the\nacceptance criteria',
color: theme.palette.accent.secondary,
},
{
name: 'GitHub',
icon: 'PR',
desc: 'В PR-комментарии для авто-ответа на review-комментарии.',
syntax: '@openswe address\nthe review comments',
color: theme.palette.accent.tertiary,
},
];
triggers.forEach(function (t, idx) {
const cx = 0.5 + idx * (cardW + gap);
slide.addShape(pres.ShapeType.roundRect, {
x: cx, y: cardY, w: cardW, h: cardH,
fill: { color: theme.palette.bg.elevated },
line: { color: t.color, width: 1.2 },
rectRadius: 0.08,
});
// Icon badge
slide.addShape(pres.ShapeType.roundRect, {
x: cx + 0.2, y: cardY + 0.2, w: 0.55, h: 0.55,
fill: { color: t.color },
line: { color: t.color, width: 1 },
rectRadius: 0.08,
});
slide.addText(t.icon, {
x: cx + 0.2, y: cardY + 0.2, w: 0.55, h: 0.55,
fontFace: helpers.withFallback(theme.fonts.ui),
fontSize: 18,
color: theme.palette.text.inverse,
bold: true,
align: 'center',
valign: 'middle',
});
// Name
slide.addText(t.name, {
x: cx + 0.85, y: cardY + 0.2, w: cardW - 1.0, h: 0.55,
fontFace: helpers.withFallback(theme.fonts.ui),
fontSize: theme.sizes.h3,
color: theme.palette.text.primary,
bold: true,
valign: 'middle',
});
// Desc
slide.addText(t.desc, {
x: cx + 0.2, y: cardY + 0.9, w: cardW - 0.4, h: 1.05,
fontFace: helpers.withFallback(theme.fonts.ui),
fontSize: theme.sizes.body,
color: theme.palette.text.secondary,
valign: 'top',
});
// Syntax code
slide.addShape(pres.ShapeType.roundRect, {
x: cx + 0.2, y: cardY + 2.05, w: cardW - 0.4, h: 1.25,
fill: { color: theme.palette.bg.code },
line: { color: theme.palette.border.subtle, width: 0.5 },
rectRadius: 0.06,
});
slide.addText(t.syntax, {
x: cx + 0.3, y: cardY + 2.1, w: cardW - 0.6, h: 1.15,
fontFace: helpers.withFallback(theme.fonts.code),
fontSize: theme.sizes.code,
color: theme.palette.text.primary,
valign: 'top',
});
});
helpers.addPageNumber(slide, pres, theme, 13);
helpers.addSourceLine(slide, pres, theme, {
source: 'research/per-tech/openswe.md: 90-97',
});
return slide;
}
module.exports = { buildTriggersOverview };
@@ -0,0 +1,68 @@
/**
* slides/14-triggers-thread-id.js
* ----------------------------------------------------------------------------
* Slide 14 -- Trigger routing + deterministic thread_id
*/
'use strict';
const { helpers, layouts } = require('../../design-system');
function buildTriggerRouting(pres, theme) {
const slide = pres.addSlide();
helpers.slideBase(slide, pres, theme);
helpers.addHeader(slide, pres, theme, {
eyebrow: 'STAGE 4',
sectionNumber: 4,
title: 'Triggers: thread_id routing',
});
helpers.addCodeBlock(slide, pres, theme, {
x: 0.5, y: layouts.CONTENT_TOP, w: 5.6, h: 3.5,
language: 'python',
code: [
'# open_swe/triggers/router.py',
'def thread_id_for(source, ref, comment_id):',
' # Deterministic thread_id:',
' # все follow-up попадают в один run.',
' if source == "slack":',
' return f"slack:{ref}"',
' if source == "linear":',
' return f"linear:{ref}"',
' if source == "github":',
' return f"github:{ref}:{comment_id}"',
' raise ValueError(source)',
].join('\n'),
filePath: 'open_swe/triggers/router.py',
startLine: 1,
highlightLines: [2, 3, 4, 5, 6, 7, 8, 9],
});
helpers.addCallout(slide, pres, theme, {
x: 6.3, y: layouts.CONTENT_TOP, w: 3.2, h: 1.55,
kind: 'info',
title: 'Routing',
text:
'thread_id из (source, ref). ' +
'Если run бежит -- новые ' +
'сообщения ждут в очереди.',
});
helpers.addCallout(slide, pres, theme, {
x: 6.3, y: 3.05, w: 3.2, h: 1.8,
kind: 'warning',
title: 'Concurrency',
text:
'Один thread = один run. ' +
'Параллельность через ' +
'отдельный thread_id ' +
'(например, отдельный Slack).',
});
helpers.addPageNumber(slide, pres, theme, 14);
helpers.addSourceLine(slide, pres, theme, {
source: 'research/per-tech/openswe.md: 96-97, 131-133',
});
return slide;
}
module.exports = { buildTriggerRouting };
@@ -0,0 +1,74 @@
/**
* slides/15-webhook-endpoints.js
* ----------------------------------------------------------------------------
* Slide 15 -- Webhook endpoints: FastAPI handlers
* /webhooks/{github,linear,slack}
*/
'use strict';
const { helpers, layouts } = require('../../design-system');
function buildWebhookEndpoints(pres, theme) {
const slide = pres.addSlide();
helpers.slideBase(slide, pres, theme);
helpers.addHeader(slide, pres, theme, {
eyebrow: 'STAGE 4',
sectionNumber: 4,
title: 'Webhook endpoints',
});
helpers.addCodeBlock(slide, pres, theme, {
x: 0.5, y: layouts.CONTENT_TOP, w: 6.0, h: 3.5,
language: 'python',
code: [
'# apps/open-swe/webhooks.py',
'from fastapi import APIRouter, Request',
'from open_swe.triggers.router import (',
' thread_id_for,',
')',
'',
'router = APIRouter()',
'',
'@router.post("/webhooks/slack")',
'async def slack_webhook(req: Request):',
' payload = await req.json()',
' if "@open-swe" not in payload["text"]:',
' return {"ok": True}',
' await enqueue_run(thread_id_for(',
' "slack", payload["channel"]))',
].join('\n'),
filePath: 'apps/open-swe/webhooks.py',
startLine: 1,
highlightLines: [9, 10, 11, 12, 13, 14, 15],
});
helpers.addCallout(slide, pres, theme, {
x: 6.7, y: layouts.CONTENT_TOP, w: 2.8, h: 1.55,
kind: 'success',
title: 'Idempotency',
text:
'Webhook вычисляет thread_id ' +
'и проверяет наличие run. ' +
'Если есть -- enqueue, ' +
'иначе новый run.',
});
helpers.addCallout(slide, pres, theme, {
x: 6.7, y: 2.95, w: 2.8, h: 1.9,
kind: 'warning',
title: 'Signature',
text:
'Все handlers обязаны ' +
'проверять X-Signature от ' +
'Slack / Linear / GitHub. ' +
'Не доверяйте без verify.',
});
helpers.addPageNumber(slide, pres, theme, 15);
helpers.addSourceLine(slide, pres, theme, {
source: 'research/per-tech/openswe.md + FastAPI webhook patterns',
});
return slide;
}
module.exports = { buildWebhookEndpoints };
@@ -0,0 +1,72 @@
/**
* slides/16-dashboard-ui.js
* ----------------------------------------------------------------------------
* Slide 16 -- Dashboard UI: TanStack Start + Vite
* UI structure + how it talks to backend.
*/
'use strict';
const { helpers, layouts } = require('../../design-system');
function buildDashboardUI(pres, theme) {
const slide = pres.addSlide();
helpers.slideBase(slide, pres, theme);
helpers.addHeader(slide, pres, theme, {
eyebrow: 'STAGE 4',
sectionNumber: 4,
title: 'Dashboard UI',
});
helpers.addCodeBlock(slide, pres, theme, {
x: 0.5, y: layouts.CONTENT_TOP, w: 5.7, h: 3.5,
language: 'bash',
code: [
'# apps/open-swe-ui/',
'# TanStack Start + Vite + React 19',
'',
'src/routes/',
' __root.tsx',
' index.tsx # thread list',
' thread.$id.tsx # chat',
' settings.tsx',
'src/components/',
' ChatMessage.tsx',
' DiffViewer.tsx',
'src/lib/',
' client.ts # OpenSWEClient',
].join('\n'),
filePath: 'apps/open-swe-ui/tree.sh',
startLine: 1,
});
helpers.addCallout(slide, pres, theme, {
x: 6.4, y: layouts.CONTENT_TOP, w: 3.1, h: 1.5,
kind: 'info',
title: 'Только UI',
text:
'Dashboard -- presentation ' +
'layer. Агентская логика в ' +
'Python backend. UI через ' +
'REST + SSE.',
});
helpers.addCallout(slide, pres, theme, {
x: 6.4, y: 2.85, w: 3.1, h: 2.0,
kind: 'success',
title: 'Features',
text:
'* GitHub OAuth login\n' +
'* thread list + filters\n' +
'* chat with streaming\n' +
'* diff viewer для PR\n' +
'* per-user settings',
});
helpers.addPageNumber(slide, pres, theme, 16);
helpers.addSourceLine(slide, pres, theme, {
source: 'research/per-tech/openswe.md: 270-287',
});
return slide;
}
module.exports = { buildDashboardUI };
@@ -0,0 +1,73 @@
/**
* slides/17-per-user-settings.js
* ----------------------------------------------------------------------------
* Slide 17 -- Per-user settings + team defaults + repos
*/
'use strict';
const { helpers, layouts } = require('../../design-system');
function buildPerUserSettings(pres, theme) {
const slide = pres.addSlide();
helpers.slideBase(slide, pres, theme);
helpers.addHeader(slide, pres, theme, {
eyebrow: 'STAGE 4',
sectionNumber: 4,
title: 'Per-user настройки',
});
helpers.addCodeBlock(slide, pres, theme, {
x: 0.5, y: layouts.CONTENT_TOP, w: 5.6, h: 3.5,
language: 'python',
code: [
'# settings schema (per-user)',
'USER_DEFAULTS = {',
' "model": "anthropic:claude-opus-4-6",',
' "profile": "balanced",',
' "max_steps": 80,',
' "auto_open_pr": False,',
' "require_approval": True,',
' "extra_repos": ["acme/internal-tools"],',
'}',
'',
'# team defaults (allowlist)',
'TEAM_DEFAULTS = {',
' "sandbox_backend": "ModalBackend",',
' "allowed_repos": ["acme/*"],',
'}',
].join('\n'),
filePath: 'open_swe/settings.py',
startLine: 1,
highlightLines: [3, 4, 5, 6, 7, 11, 12],
});
helpers.addCallout(slide, pres, theme, {
x: 6.3, y: layouts.CONTENT_TOP, w: 3.2, h: 1.55,
kind: 'info',
title: 'User mappings',
text:
'GitHub login -> Slack user -> ' +
'Linear user. Один человек ' +
'получает свой профиль во ' +
'всех трех каналах.',
});
helpers.addCallout(slide, pres, theme, {
x: 6.3, y: 2.95, w: 3.2, h: 1.9,
kind: 'warning',
title: 'Precedence',
text:
'user > team > global. Per-user ' +
'override работает для всех ' +
'полей, кроме allowed_repos ' +
'-- это team-level allowlist.',
});
helpers.addPageNumber(slide, pres, theme, 17);
helpers.addSourceLine(slide, pres, theme, {
source: 'research/per-tech/openswe.md: 270-280',
});
return slide;
}
module.exports = { buildPerUserSettings };
@@ -0,0 +1,94 @@
/**
* slides/18-customization-6-points.js
* ----------------------------------------------------------------------------
* Slide 18 -- 6 точек кастомизации
* sandbox / model / tools / triggers / prompt / middleware
*/
'use strict';
const { helpers, layouts } = require('../../design-system');
function buildCustomization6(pres, theme) {
const slide = pres.addSlide();
helpers.slideBase(slide, pres, theme);
helpers.addHeader(slide, pres, theme, {
eyebrow: 'STAGE 4',
sectionNumber: 4,
title: '6 точек кастомизации',
});
const items = [
{ num: '1', name: 'Sandbox provider', code: 'backend=ModalBackend(...)' },
{ num: '2', name: 'Model', code: 'init_chat_model("anthropic:...")' },
{ num: '3', name: 'Tools', code: 'tools=[execute, fetch_url, ...]' },
{ num: '4', name: 'Triggers', code: 'router.add_route("/my", my_handler)' },
{ num: '5', name: 'System prompt', code: 'construct_system_prompt(repo_dir, ...)' },
{ num: '6', name: 'Middleware', code: 'middleware=[AuditLogger(), ...]' },
];
const cardY = layouts.CONTENT_TOP;
const cardW = 2.95;
const cardH = 1.65;
const gap = 0.13;
items.forEach(function (it, idx) {
const col = idx % 3;
const row = Math.floor(idx / 3);
const cx = 0.5 + col * (cardW + gap);
const cy = cardY + row * (cardH + gap);
slide.addShape(pres.ShapeType.roundRect, {
x: cx, y: cy, w: cardW, h: cardH,
fill: { color: theme.palette.bg.elevated },
line: { color: theme.palette.border.subtle, width: 0.75 },
rectRadius: 0.08,
});
// Number badge
slide.addShape(pres.ShapeType.ellipse, {
x: cx + 0.15, y: cy + 0.15, w: 0.45, h: 0.45,
fill: { color: theme.palette.accent.primary },
line: { color: theme.palette.accent.primary, width: 1 },
});
slide.addText(it.num, {
x: cx + 0.15, y: cy + 0.15, w: 0.45, h: 0.45,
fontFace: helpers.withFallback(theme.fonts.ui),
fontSize: 18,
color: theme.palette.text.inverse,
bold: true,
align: 'center',
valign: 'middle',
});
slide.addText(it.name, {
x: cx + 0.7, y: cy + 0.15, w: cardW - 0.85, h: 0.45,
fontFace: helpers.withFallback(theme.fonts.ui),
fontSize: theme.sizes.h3,
color: theme.palette.text.primary,
bold: true,
valign: 'middle',
});
// Code block
slide.addShape(pres.ShapeType.rect, {
x: cx + 0.15, y: cy + 0.7, w: cardW - 0.3, h: cardH - 0.85,
fill: { color: theme.palette.bg.code },
line: { color: theme.palette.border.subtle, width: 0.5 },
});
slide.addText(it.code, {
x: cx + 0.25, y: cy + 0.75, w: cardW - 0.5, h: cardH - 0.95,
fontFace: helpers.withFallback(theme.fonts.code),
fontSize: theme.sizes.code,
color: theme.palette.text.primary,
valign: 'top',
});
});
helpers.addPageNumber(slide, pres, theme, 18);
helpers.addSourceLine(slide, pres, theme, {
source: 'github.com/langchain-ai/open-swe/blob/main/CUSTOMIZATION.md',
});
return slide;
}
module.exports = { buildCustomization6 };
@@ -0,0 +1,96 @@
/**
* slides/19-three-graphs.js
* ----------------------------------------------------------------------------
* Slide 19 -- Three graphs: agent / reviewer / analyzer
* Multi-graph architecture.
*/
'use strict';
const { helpers, layouts } = require('../../design-system');
function buildThreeGraphs(pres, theme) {
const slide = pres.addSlide();
helpers.slideBase(slide, pres, theme);
helpers.addHeader(slide, pres, theme, {
eyebrow: 'STAGE 4',
sectionNumber: 4,
title: 'Three graphs',
});
const graphs = [
{
name: 'agent graph',
desc: 'Основной deep-agent: planner + coder + tools + subagents.',
nodes: 'START -> plan -> execute -> review -> open_pr -> END',
},
{
name: 'reviewer graph',
desc: 'CI-стиль: lint, type-check, tests, security-scan. Без LLM-агента.',
nodes: 'START -> run_ci -> parse -> report -> END',
},
{
name: 'analyzer graph',
desc: 'Анализ ретроспективы: что заняло больше шагов, где loop, где cost spikes.',
nodes: 'START -> load_trace -> aggregate -> summarize -> END',
},
];
const cardY = layouts.CONTENT_TOP;
const cardW = 9.0;
const cardH = 1.05;
const gap = 0.13;
graphs.forEach(function (g, idx) {
const cy = cardY + idx * (cardH + gap);
slide.addShape(pres.ShapeType.roundRect, {
x: 0.5, y: cy, w: cardW, h: cardH,
fill: { color: theme.palette.bg.elevated },
line: { color: theme.palette.border.subtle, width: 0.75 },
rectRadius: 0.08,
});
// Name badge
slide.addShape(pres.ShapeType.rect, {
x: 0.6, y: cy + 0.12, w: 2.4, h: 0.4,
fill: { color: theme.palette.accent.primary },
line: { color: theme.palette.accent.primary, width: 1 },
rectRadius: 0.04,
});
slide.addText(g.name, {
x: 0.6, y: cy + 0.12, w: 2.4, h: 0.4,
fontFace: helpers.withFallback(theme.fonts.code),
fontSize: theme.sizes.code,
color: theme.palette.text.inverse,
bold: true,
align: 'center',
valign: 'middle',
});
// Desc
slide.addText(g.desc, {
x: 3.1, y: cy + 0.1, w: 6.3, h: 0.4,
fontFace: helpers.withFallback(theme.fonts.ui),
fontSize: theme.sizes.body,
color: theme.palette.text.secondary,
valign: 'middle',
});
// Node line
slide.addText(g.nodes, {
x: 0.6, y: cy + 0.6, w: cardW - 0.2, h: 0.4,
fontFace: helpers.withFallback(theme.fonts.code),
fontSize: theme.sizes.code,
color: theme.palette.text.primary,
valign: 'middle',
});
});
helpers.addPageNumber(slide, pres, theme, 19);
helpers.addSourceLine(slide, pres, theme, {
source: 'research/per-tech/openswe.md: 165, 226-247',
});
return slide;
}
module.exports = { buildThreeGraphs };
@@ -0,0 +1,74 @@
/**
* slides/20-observability.js
* ----------------------------------------------------------------------------
* Slide 20 -- Observability: Datadog + LangSmith
* Tracing + metrics.
*/
'use strict';
const { helpers, layouts } = require('../../design-system');
function buildObservability(pres, theme) {
const slide = pres.addSlide();
helpers.slideBase(slide, pres, theme);
helpers.addHeader(slide, pres, theme, {
eyebrow: 'STAGE 4',
sectionNumber: 4,
title: 'Observability',
});
helpers.addCodeBlock(slide, pres, theme, {
x: 0.5, y: layouts.CONTENT_TOP, w: 5.6, h: 3.5,
language: 'python',
code: [
'# observability.py',
'from datadog import statsd',
'from langchain_core.tracers import (',
' LangChainTracer,',
')',
'',
'tracer = LangChainTracer(',
' project_name="open-swe-prod",',
')',
'',
'def record_step(thread_id, duration_s):',
' statsd.histogram(',
' "open_swe.step.duration_s",',
' duration_s, tags=[f"t:{thread_id}"],',
' )',
].join('\n'),
filePath: 'open_swe/observability.py',
startLine: 1,
highlightLines: [3, 4, 5, 8, 9, 10],
});
helpers.addCallout(slide, pres, theme, {
x: 6.3, y: layouts.CONTENT_TOP, w: 3.2, h: 1.7,
kind: 'info',
title: 'LangSmith',
text:
'Trace каждого run: model ' +
'calls, tool calls, retries. ' +
'Replay, debug, manual ' +
'evaluation.',
});
helpers.addCallout(slide, pres, theme, {
x: 6.3, y: 3.05, w: 3.2, h: 1.8,
kind: 'success',
title: 'Datadog',
text:
'P95 latency, tokens per ' +
'run, error rate, sandbox ' +
'spend. Alerts на cost ' +
'spikes и длинные loops.',
});
helpers.addPageNumber(slide, pres, theme, 20);
helpers.addSourceLine(slide, pres, theme, {
source: 'research/per-tech/openswe.md + Datadog / LangSmith docs',
});
return slide;
}
module.exports = { buildObservability };
@@ -0,0 +1,71 @@
/**
* slides/21-production-ready.js
* ----------------------------------------------------------------------------
* Slide 21 -- Production: чеклист prod-ready
*/
'use strict';
const { helpers, layouts } = require('../../design-system');
function buildProductionReady(pres, theme) {
const slide = pres.addSlide();
helpers.slideBase(slide, pres, theme);
helpers.addHeader(slide, pres, theme, {
eyebrow: 'STAGE 4',
sectionNumber: 4,
title: 'Production: prod-ready',
});
// Two columns of checklist items
const leftItems = [
'docker-compose / k8s',
'managed Postgres',
'Vault / sealed-secrets',
'TLS + reverse proxy',
'rate limit на /webhooks/*',
'idempotency keys',
];
const rightItems = [
'LangSmith prod-project',
'Datadog dashboards + SLO',
'sandbox cost budget',
'audit log всех PR',
'docs: runbook, on-call',
'load test 100 threads',
];
helpers.addCallout(slide, pres, theme, {
x: 0.5, y: layouts.CONTENT_TOP, w: 4.4, h: 1.6,
kind: 'success',
title: 'Infra',
text: leftItems.map(function (i) { return '+ ' + i; }).join('\n'),
});
helpers.addCallout(slide, pres, theme, {
x: 5.1, y: layouts.CONTENT_TOP, w: 4.4, h: 1.6,
kind: 'info',
title: 'Ops',
text: rightItems.map(function (i) { return '+ ' + i; }).join('\n'),
});
helpers.addCallout(slide, pres, theme, {
x: 0.5, y: 3.15, w: 9.0, h: 1.7,
kind: 'warning',
title: 'Самый частый пропуск',
text:
'"Trust the LLM" внутри sandbox. ' +
'Без надлежащей изоляции (seccomp, ' +
'network policy, ephemeral FS) агент ' +
'может сделать rm -rf или curl внутренних ' +
'сервисов. Изоляция важнее prompts.',
});
helpers.addPageNumber(slide, pres, theme, 21);
helpers.addSourceLine(slide, pres, theme, {
source: 'production-readiness checklist + SRE playbook',
});
return slide;
}
module.exports = { buildProductionReady };
+64
View File
@@ -0,0 +1,64 @@
/**
* slides/22-typescript.js
* ----------------------------------------------------------------------------
* Slide 22 -- TypeScript analogues: где есть, где нет
*/
'use strict';
const { helpers, layouts } = require('../../design-system');
function buildTypescript(pres, theme) {
const slide = pres.addSlide();
helpers.slideBase(slide, pres, theme);
helpers.addHeader(slide, pres, theme, {
eyebrow: 'STAGE 4',
sectionNumber: 4,
title: 'TypeScript: только UI',
});
helpers.addCodeBlock(slide, pres, theme, {
x: 0.5, y: layouts.CONTENT_TOP, w: 5.6, h: 3.5,
language: 'typescript',
code: [
'// apps/open-swe-ui/src/lib/client.ts',
'import { OpenSWEClient } from',
' "@openswe/client";',
'',
'const client = new OpenSWEClient({',
' langsmithApiKey: process.env.',
' LANGSMITH_API_KEY!,',
'});',
'',
'await client.invoke({',
' threadId: "issue-123",',
' prompt: "Fix the bug",',
'});',
].join('\n'),
filePath: 'apps/open-swe-ui/src/lib/client.ts',
startLine: 1,
});
helpers.addProsCons(slide, pres, theme, {
x: 6.3, y: layouts.CONTENT_TOP, w: 3.2, h: 3.5,
pros: [
'UI: полностью TS',
'TanStack Start + Vite',
'strict TS config',
'streaming SDK',
],
cons: [
'Нет TS для backend',
'Агент -- Python only',
'Webhook handlers только Py',
'SDK -- thin wrapper',
],
});
helpers.addPageNumber(slide, pres, theme, 22);
helpers.addSourceLine(slide, pres, theme, {
source: 'research/per-tech/openswe.md: 270-287',
});
return slide;
}
module.exports = { buildTypescript };
+92
View File
@@ -0,0 +1,92 @@
/**
* slides/23-pros-cons.js
* ----------------------------------------------------------------------------
* Slide 23 -- Pros / cons vs Claude Code / Devin
*/
'use strict';
const { helpers, layouts } = require('../../design-system');
function buildProsConsComparison(pres, theme) {
const slide = pres.addSlide();
helpers.slideBase(slide, pres, theme);
helpers.addHeader(slide, pres, theme, {
eyebrow: 'STAGE 4',
sectionNumber: 4,
title: 'Open SWE vs Claude Code / Devin',
});
helpers.addProsCons(slide, pres, theme, {
x: 0.5, y: layouts.CONTENT_TOP, w: 4.4, h: 1.7,
pros: [
'MIT license, форкайте',
'Pluggable sandbox',
'Triggers: Slack/Linear/GH',
'AGENTS.md convention',
'Subagents + middleware',
'Built on Deep Agents',
],
cons: [
'Не finished product',
'Sandbox = платные аккаунты',
'OAuth setup нужен',
'"Trust the LLM" модель',
'Prod deployment сложный',
'Доки быстро устаревают',
],
});
// Comparison card on the right
slide.addShape(pres.ShapeType.roundRect, {
x: 5.1, y: layouts.CONTENT_TOP, w: 4.4, h: 3.5,
fill: { color: theme.palette.bg.elevated },
line: { color: theme.palette.border.subtle, width: 1 },
rectRadius: 0.08,
});
slide.addText('VS Claude Code / Devin', {
x: 5.3, y: layouts.CONTENT_TOP + 0.1, w: 4.0, h: 0.3,
fontFace: helpers.withFallback(theme.fonts.ui),
fontSize: theme.sizes.h3,
color: theme.palette.accent.secondary,
bold: true,
});
const compareRows = [
['Audience', 'IDE', 'SaaS', 'org'],
['License', 'proprietary', 'proprietary','MIT'],
['Trigger', 'manual', 'Web UI', 'Slack/Lin/GH'],
['Sandbox', 'local+cloud', 'remote', 'pluggable'],
['Customize', 'config', 'no', 'full src'],
['Runs in', 'IDE/term', 'cloud', 'your infra'],
];
const tY = layouts.CONTENT_TOP + 0.55;
compareRows.forEach(function (row, idx) {
const ry = tY + idx * 0.45;
row.forEach(function (cell, cidx) {
const widths = [1.1, 1.0, 1.0, 1.1];
let cx = 5.3;
for (let i = 0; i < cidx; i++) cx += widths[i] + 0.05;
slide.addText(cell, {
x: cx, y: ry, w: widths[cidx], h: 0.4,
fontFace: helpers.withFallback(
cidx === 0 ? theme.fonts.ui : theme.fonts.code),
fontSize: cidx === 0 ? theme.sizes.caption : 8,
color: cidx === 3
? theme.palette.accent.tertiary
: theme.palette.text.secondary,
bold: cidx === 0,
valign: 'middle',
});
});
});
helpers.addPageNumber(slide, pres, theme, 23);
helpers.addSourceLine(slide, pres, theme, {
source: 'research/per-tech/openswe.md: 291-310',
});
return slide;
}
module.exports = { buildProsConsComparison };
+130
View File
@@ -0,0 +1,130 @@
/**
* slides/24-roadmap.js
* ----------------------------------------------------------------------------
* Slide 24 -- Roadmap: что ожидать
*/
'use strict';
const { helpers, layouts } = require('../../design-system');
function buildRoadmap(pres, theme) {
const slide = pres.addSlide();
helpers.slideBase(slide, pres, theme);
helpers.addHeader(slide, pres, theme, {
eyebrow: 'STAGE 4',
sectionNumber: 4,
title: 'Roadmap: 2026-2027',
});
// Timeline cards
const items = [
{
period: 'Q2 2026',
title: 'Stable v1',
points: [
'deep-agent harness заморожен',
'API стабилизирован',
'semver commitment',
],
},
{
period: 'Q3 2026',
title: 'Multi-tenant',
points: [
'per-org quota + billing',
'team-level audit log',
'rbac на sandbox providers',
],
},
{
period: 'Q4 2026',
title: 'More triggers',
points: [
'Jira / Azure DevOps',
'Sentry для авто-fix багов',
'PagerDuty для incident triage',
],
},
{
period: '2027',
title: 'SDK + Marketplace',
points: [
'public SDK (Python + TS)',
'plugin marketplace',
'shared subagent library',
],
},
];
const cardY = layouts.CONTENT_TOP;
const cardW = 2.2;
const cardH = 3.5;
const gap = 0.13;
items.forEach(function (it, idx) {
const cx = 0.5 + idx * (cardW + gap);
slide.addShape(pres.ShapeType.roundRect, {
x: cx, y: cardY, w: cardW, h: cardH,
fill: { color: theme.palette.bg.elevated },
line: { color: theme.palette.accent.primary, width: 1 },
rectRadius: 0.08,
});
// Period header
slide.addShape(pres.ShapeType.rect, {
x: cx, y: cardY, w: cardW, h: 0.5,
fill: { color: theme.palette.accent.primary },
line: { color: theme.palette.accent.primary, width: 1 },
});
slide.addText(it.period, {
x: cx, y: cardY, w: cardW, h: 0.5,
fontFace: helpers.withFallback(theme.fonts.ui),
fontSize: theme.sizes.h3,
color: theme.palette.text.inverse,
bold: true,
align: 'center',
valign: 'middle',
});
// Title
slide.addText(it.title, {
x: cx + 0.15, y: cardY + 0.65, w: cardW - 0.3, h: 0.4,
fontFace: helpers.withFallback(theme.fonts.ui),
fontSize: theme.sizes.h3,
color: theme.palette.text.primary,
bold: true,
});
// Divider
slide.addShape(pres.ShapeType.line, {
x: cx + 0.15, y: cardY + 1.1, w: cardW - 0.3, h: 0,
line: { color: theme.palette.border.subtle, width: 0.75 },
});
// Bullets
const bulletItems = it.points.map(function (p) {
return {
text: '> ' + p + '\n',
options: {
fontFace: helpers.withFallback(theme.fonts.code),
fontSize: 10,
color: theme.palette.text.secondary,
},
};
});
slide.addText(bulletItems, {
x: cx + 0.15, y: cardY + 1.2, w: cardW - 0.3, h: cardH - 1.3,
valign: 'top',
paraSpaceAfter: 4,
});
});
helpers.addPageNumber(slide, pres, theme, 24);
helpers.addSourceLine(slide, pres, theme, {
source: 'github.com/langchain-ai/open-swe/issues + blog.langchain.com',
});
return slide;
}
module.exports = { buildRoadmap };
+111
View File
@@ -0,0 +1,111 @@
/**
* slides/section4-openswe/compile.js
* ----------------------------------------------------------------------------
* Compiles all Open SWE section slides into section4.pptx.
*
* Usage:
* node compile.js
*
* Output:
* ../section4.pptx (relative to this file)
* ../section4.pdf (via libreoffice)
* ../section4-page-N.png (preview images via pdftoppm)
*/
'use strict';
const path = require('path');
const fs = require('fs');
const pptxgen = require('pptxgenjs');
const ds = require('../../design-system');
const { theme, helpers } = ds;
// Import all slide builders in order.
const slides = [
require('./01-cover'),
require('./02-what-is-openswe'),
require('./03-architecture-overview'),
require('./04-create-deep-agent'),
require('./05-agents-md-context'),
require('./06-middleware'),
require('./07-sandbox-providers'),
require('./08-sandbox-imports'),
require('./09-installation-1'),
require('./10-installation-2'),
require('./11-github-app'),
require('./12-langsmith'),
require('./13-triggers-overview'),
require('./14-triggers-thread-id'),
require('./15-webhook-endpoints'),
require('./16-dashboard-ui'),
require('./17-per-user-settings'),
require('./18-customization-6-points'),
require('./19-three-graphs'),
require('./20-observability'),
require('./21-production-ready'),
require('./22-typescript'),
require('./23-pros-cons'),
require('./24-roadmap'),
];
async function main() {
console.log('[compile] building section4.pptx with ' + slides.length + ' slides');
const pres = new pptxgen();
pres.layout = 'LAYOUT_16x9';
pres.title = 'lc-evo-deck / Stage 4: Open SWE';
pres.subject = 'LangChain Evolution Deck -- Open SWE section';
pres.company = 'lc-evo-deck';
for (let i = 0; i < slides.length; i++) {
const mod = slides[i];
const builder = pickBuilder(mod);
if (typeof builder !== 'function') {
throw new Error('slide ' + (i + 1) + ': no builder function');
}
builder(pres, theme);
}
// Output: one level up from this file, into sec4-openswe/ root.
const outDir = path.resolve(__dirname, '..', '..');
const outPath = path.join(outDir, 'section4.pptx');
await pres.writeFile({ fileName: outPath });
console.log('[compile] wrote ' + outPath);
return outPath;
}
function pickBuilder(mod) {
// Each slide file exports either buildX or default.
if (typeof mod.buildCover === 'function') return mod.buildCover;
if (typeof mod.buildWhatIsOpenSWE === 'function') return mod.buildWhatIsOpenSWE;
if (typeof mod.buildArchitectureOverview === 'function') return mod.buildArchitectureOverview;
if (typeof mod.buildCreateDeepAgent === 'function') return mod.buildCreateDeepAgent;
if (typeof mod.buildAgentsMdConvention === 'function') return mod.buildAgentsMdConvention;
if (typeof mod.buildMiddleware === 'function') return mod.buildMiddleware;
if (typeof mod.buildSandboxProviders === 'function') return mod.buildSandboxProviders;
if (typeof mod.buildSandboxImports === 'function') return mod.buildSandboxImports;
if (typeof mod.buildInstallPart1 === 'function') return mod.buildInstallPart1;
if (typeof mod.buildInstallPart2 === 'function') return mod.buildInstallPart2;
if (typeof mod.buildGithubApp === 'function') return mod.buildGithubApp;
if (typeof mod.buildLangSmithSetup === 'function') return mod.buildLangSmithSetup;
if (typeof mod.buildTriggersOverview === 'function') return mod.buildTriggersOverview;
if (typeof mod.buildTriggerRouting === 'function') return mod.buildTriggerRouting;
if (typeof mod.buildWebhookEndpoints === 'function') return mod.buildWebhookEndpoints;
if (typeof mod.buildDashboardUI === 'function') return mod.buildDashboardUI;
if (typeof mod.buildPerUserSettings === 'function') return mod.buildPerUserSettings;
if (typeof mod.buildCustomization6 === 'function') return mod.buildCustomization6;
if (typeof mod.buildThreeGraphs === 'function') return mod.buildThreeGraphs;
if (typeof mod.buildObservability === 'function') return mod.buildObservability;
if (typeof mod.buildProductionReady === 'function') return mod.buildProductionReady;
if (typeof mod.buildTypescript === 'function') return mod.buildTypescript;
if (typeof mod.buildProsConsComparison === 'function') return mod.buildProsConsComparison;
if (typeof mod.buildRoadmap === 'function') return mod.buildRoadmap;
return null;
}
main().catch(function (err) {
console.error('[compile] failed:', err);
process.exit(1);
});
Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.
Binary file not shown.