From cad01a410ec0305c47b76ad8e6bd90d6e7772258 Mon Sep 17 00:00:00 2001 From: dahlinomine Date: Fri, 20 Mar 2026 10:27:58 +0000 Subject: [PATCH] feat(adapter): add Deep Agents export adapter Adds `gitagent export --format deepagents` which generates a ready-to-run Python file using the deepagents SDK. - Translates agent.yaml + SOUL.md + skills into a create_deep_agent() call - Skills become @tool stubs with TODO placeholders - Compliance constraints injected into system_prompt - Sub-agent hints surfaced as comments with upstream docs link - Multi-agent topology note links to deepagents overview Install: pip install deepagents --- src/adapters/deepagents.ts | 164 +++++++++++++++++++++++++++++++++++++ src/adapters/index.ts | 1 + src/commands/export.ts | 8 +- 3 files changed, 171 insertions(+), 2 deletions(-) create mode 100644 src/adapters/deepagents.ts diff --git a/src/adapters/deepagents.ts b/src/adapters/deepagents.ts new file mode 100644 index 0000000..b5564bb --- /dev/null +++ b/src/adapters/deepagents.ts @@ -0,0 +1,164 @@ +import { resolve, join } from 'node:path'; +import { loadAgentManifest, loadFileIfExists } from '../utils/loader.js'; +import { loadAllSkills, getAllowedTools } from '../utils/skill-loader.js'; + +/** + * Export a gitagent directory to a Deep Agents (deepagents) Python agent. + * + * Generates a ready-to-run Python file using the `deepagents` SDK + * (`pip install deepagents`). Deep Agents is built on top of LangChain + * and the LangGraph runtime, providing built-in task planning, filesystem + * context management, subagent-spawning, and long-term memory. + * + * Compliance constraints from agent.yaml are injected as system_prompt rules. + * Skills become tool stubs that can be swapped for real implementations. + */ +export function exportToDeepAgents(dir: string): string { + const agentDir = resolve(dir); + const manifest = loadAgentManifest(agentDir); + + const systemPrompt = buildSystemPrompt(agentDir, manifest); + const tools = buildToolDefinitions(agentDir); + const hasSubAgents = manifest.agents && Object.keys(manifest.agents).length > 0; + const modelName = resolveModelName(manifest.model?.preferred); + + const lines: string[] = []; + + lines.push('"""'); + lines.push(`Deep Agents agent for ${manifest.name} v${manifest.version}`); + lines.push('Generated by gitagent export --format deepagents'); + lines.push('Install: pip install deepagents'); + lines.push('"""'); + lines.push(''); + lines.push('from deepagents import create_deep_agent'); + lines.push('from langchain_core.tools import tool'); + lines.push(''); + + if (tools.length > 0) { + lines.push('# --- Tools (from gitagent skills) ---'); + for (const t of tools) { + const funcName = t.name.replace(/[^a-zA-Z0-9]/g, '_'); + lines.push(''); + lines.push('@tool'); + lines.push(`def ${funcName}(input: str) -> str:`); + lines.push(` """${t.description}"""`); + lines.push(` # TODO: implement tool logic`); + lines.push(` raise NotImplementedError("${funcName} not yet implemented")`); + } + lines.push(''); + lines.push(`tools = [${tools.map(t => t.name.replace(/[^a-zA-Z0-9]/g, '_')).join(', ')}]`); + } else { + lines.push('tools = []'); + } + + lines.push(''); + lines.push('SYSTEM_PROMPT = """' + systemPrompt.replace(/"""/g, '\\"\\"\\"') + '"""'); + lines.push(''); + + lines.push('# --- Agent ---'); + lines.push(`agent = create_deep_agent(`); + lines.push(` tools=tools,`); + lines.push(` system_prompt=SYSTEM_PROMPT,`); + if (modelName) { + lines.push(` model="${modelName}",`); + } + lines.push(`)`); + + if (hasSubAgents) { + lines.push(''); + lines.push('# --- Sub-Agents ---'); + lines.push('# Sub-agents defined in agent.yaml can be modelled as additional'); + lines.push('# Deep Agents instances passed as tools or orchestrated via a supervisor.'); + lines.push('# See: https://docs.langchain.com/oss/python/deepagents/overview'); + for (const [name] of Object.entries(manifest.agents ?? {})) { + lines.push(`# Sub-agent: ${name}`); + } + } + + lines.push(''); + lines.push(`if __name__ == "__main__":`); + lines.push(` print("Agent: ${manifest.name} v${manifest.version}")`); + lines.push(` while True:`); + lines.push(` user_input = input("You: ").strip()`); + lines.push(` if not user_input or user_input.lower() in ("exit", "quit"):`); + lines.push(` break`); + lines.push(` result = agent.invoke(`); + lines.push(` {"messages": [{"role": "user", "content": user_input}]}`); + lines.push(` )`); + lines.push(` messages = result.get("messages", [])`); + lines.push(` last_ai = next(`); + lines.push(` (m for m in reversed(messages) if getattr(m, "type", None) == "ai"),`); + lines.push(` None,`); + lines.push(` )`); + lines.push(` if last_ai:`); + lines.push(` print(f"Agent: {last_ai.content}")`); + + return lines.join('\n'); +} + +function buildSystemPrompt( + agentDir: string, + manifest: ReturnType, +): string { + const parts: string[] = []; + + const soul = loadFileIfExists(join(agentDir, 'SOUL.md')); + if (soul) parts.push(soul); + + const rules = loadFileIfExists(join(agentDir, 'RULES.md')); + if (rules) parts.push(`## Rules\n${rules}`); + + const skillsDir = join(agentDir, 'skills'); + const skills = loadAllSkills(skillsDir); + for (const skill of skills) { + const allowedTools = getAllowedTools(skill.frontmatter); + const toolsNote = + allowedTools.length > 0 ? `\nAllowed tools: ${allowedTools.join(', ')}` : ''; + parts.push( + `## Skill: ${skill.frontmatter.name}\n${skill.frontmatter.description}${toolsNote}\n\n${skill.instructions}`, + ); + } + + if (manifest.compliance) { + const c = manifest.compliance; + const constraints: string[] = ['## Compliance Constraints']; + if (c.communications?.fair_balanced) + constraints.push('- All outputs must be fair and balanced (FINRA 2210)'); + if (c.communications?.no_misleading) + constraints.push('- Never make misleading or promissory statements'); + if (c.data_governance?.pii_handling === 'redact') + constraints.push('- Redact all PII from outputs'); + if (c.supervision?.human_in_the_loop === 'always') + constraints.push('- All decisions require human approval'); + if (manifest.compliance.segregation_of_duties) { + const sod = manifest.compliance.segregation_of_duties; + if (sod.conflicts) { + constraints.push('- Segregation of duties conflicts:'); + for (const [a, b] of sod.conflicts) { + constraints.push(` - "${a}" and "${b}" may not be held by the same agent`); + } + } + } + if (constraints.length > 1) parts.push(constraints.join('\n')); + } + + return parts.join('\n\n'); +} + +interface ToolDef { + name: string; + description: string; +} + +function buildToolDefinitions(agentDir: string): ToolDef[] { + const skills = loadAllSkills(join(agentDir, 'skills')); + return skills.map(s => ({ + name: s.frontmatter.name, + description: s.frontmatter.description ?? s.frontmatter.name, + })); +} + +function resolveModelName(model?: string): string | undefined { + if (!model) return undefined; + return model; +} diff --git a/src/adapters/index.ts b/src/adapters/index.ts index 487c132..2b73184 100644 --- a/src/adapters/index.ts +++ b/src/adapters/index.ts @@ -5,3 +5,4 @@ export { exportToCrewAI } from './crewai.js'; export { exportToOpenClawString, exportToOpenClaw } from './openclaw.js'; export { exportToNanobotString, exportToNanobot } from './nanobot.js'; export { exportToCopilotString, exportToCopilot } from './copilot.js'; +export { exportToDeepAgents } from './deepagents.js'; diff --git a/src/commands/export.ts b/src/commands/export.ts index 465b040..9b92a5d 100644 --- a/src/commands/export.ts +++ b/src/commands/export.ts @@ -12,6 +12,7 @@ import { } from '../adapters/index.js'; import { exportToLyzrString } from '../adapters/lyzr.js'; import { exportToGitHubString } from '../adapters/github.js'; +import { exportToDeepAgents } from '../adapters/deepagents.js'; interface ExportOptions { format: string; @@ -21,7 +22,7 @@ interface ExportOptions { export const exportCommand = new Command('export') .description('Export agent to other formats') - .requiredOption('-f, --format ', 'Export format (system-prompt, claude-code, openai, crewai, openclaw, nanobot, lyzr, github, copilot)') + .requiredOption('-f, --format ', 'Export format (system-prompt, claude-code, openai, crewai, openclaw, nanobot, lyzr, github, copilot, deepagents)') .option('-d, --dir ', 'Agent directory', '.') .option('-o, --output ', 'Output file path') .action(async (options: ExportOptions) => { @@ -61,9 +62,12 @@ export const exportCommand = new Command('export') case 'copilot': result = exportToCopilotString(dir); break; + case 'deepagents': + result = exportToDeepAgents(dir); + break; default: error(`Unknown format: ${options.format}`); - info('Supported formats: system-prompt, claude-code, openai, crewai, openclaw, nanobot, lyzr, github, copilot'); + info('Supported formats: system-prompt, claude-code, openai, crewai, openclaw, nanobot, lyzr, github, copilot, deepagents'); process.exit(1); }