refactor: shared source — replace per-group agent-runner copies with single RO mount
Replace the per-group agent-runner-src copy model with a single shared read-only mount. Source and skills are now RO + shared; personality, config, working files, and Claude state stay RW + per-group. Key changes: - Mount container/agent-runner/src/ RO at /app/src (all groups share one copy) - Mount container/skills/ RO at /app/skills; per-group skill selection via symlinks in .claude-shared/skills/ based on container.json "skills" field - Mount container.json as nested RO bind on top of RW group dir - Move all NANOCLAW_* env vars to container.json (runner reads at startup) - New runner config.ts module replaces process.env reads - Move command gate (filtered/admin) from container to host router - Dockerfile: remove source COPY, split CLI installs (claude-code last), move agent-runner deps above CLIs for better layer caching - Add writeOutboundDirect for router denial responses - Design doc at docs/shared-src.md Not included (follow-up): DB migration to drop agent_provider columns, cleanup of orphaned agent-runner-src directories. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,14 +4,8 @@
|
||||
* Runs inside a container. All IO goes through the session DB.
|
||||
* No stdin, no stdout markers, no IPC files.
|
||||
*
|
||||
* Config:
|
||||
* - SESSION_INBOUND_DB_PATH: path to host-owned inbound DB (default: /workspace/inbound.db)
|
||||
* - SESSION_OUTBOUND_DB_PATH: path to container-owned outbound DB (default: /workspace/outbound.db)
|
||||
* - SESSION_HEARTBEAT_PATH: heartbeat file path (default: /workspace/.heartbeat)
|
||||
* - AGENT_PROVIDER: any registered provider name (default: claude). The
|
||||
* set of registered providers is whatever `providers/index.ts` imports.
|
||||
* - NANOCLAW_ASSISTANT_NAME: assistant name for transcript archiving
|
||||
* - NANOCLAW_ADMIN_USER_IDS: comma-separated user IDs allowed to run admin commands
|
||||
* Config is read from /workspace/agent/container.json (mounted RO).
|
||||
* Only TZ and OneCLI networking vars come from env.
|
||||
*
|
||||
* Mount structure:
|
||||
* /workspace/
|
||||
@@ -19,14 +13,19 @@
|
||||
* outbound.db ← container-owned session DB
|
||||
* .heartbeat ← container touches for liveness detection
|
||||
* outbox/ ← outbound files
|
||||
* agent/ ← agent group folder (CLAUDE.md, skills, working files)
|
||||
* .claude/ ← Claude SDK session data
|
||||
* agent/ ← agent group folder (CLAUDE.md, container.json, working files)
|
||||
* container.json ← per-group config (RO nested mount)
|
||||
* global/ ← shared global memory (RO)
|
||||
* /app/src/ ← shared agent-runner source (RO)
|
||||
* /app/skills/ ← shared skills (RO)
|
||||
* /home/node/.claude/ ← Claude SDK state + skill symlinks (RW)
|
||||
*/
|
||||
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
import { loadConfig } from './config.js';
|
||||
import { buildSystemPromptAddendum } from './destinations.js';
|
||||
// Providers barrel — each enabled provider self-registers on import.
|
||||
// Provider skills append imports to providers/index.ts.
|
||||
@@ -41,21 +40,11 @@ function log(msg: string): void {
|
||||
const CWD = '/workspace/agent';
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const providerName = (process.env.AGENT_PROVIDER || 'claude').toLowerCase() as ProviderName;
|
||||
const assistantName = process.env.NANOCLAW_ASSISTANT_NAME;
|
||||
const adminUserIds = new Set(
|
||||
(process.env.NANOCLAW_ADMIN_USER_IDS || '')
|
||||
.split(',')
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean),
|
||||
);
|
||||
const config = loadConfig();
|
||||
const providerName = config.provider.toLowerCase() as ProviderName;
|
||||
|
||||
log(`Starting v2 agent-runner (provider: ${providerName})`);
|
||||
|
||||
// Destinations addendum is the only runtime-generated context we inject.
|
||||
// Global CLAUDE.md is loaded by Claude Code from /workspace/agent/CLAUDE.md
|
||||
// (which imports /workspace/global/CLAUDE.md via @-syntax) — no need to
|
||||
// read it manually anymore.
|
||||
const instructions = buildSystemPromptAddendum();
|
||||
|
||||
// Discover additional directories mounted at /workspace/extra/*
|
||||
@@ -77,34 +66,22 @@ async function main(): Promise<void> {
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const mcpServerPath = path.join(__dirname, 'mcp-tools', 'index.ts');
|
||||
|
||||
// Build MCP servers config: nanoclaw built-in + any additional from host
|
||||
// Build MCP servers config: nanoclaw built-in + any from container.json
|
||||
const mcpServers: Record<string, { command: string; args: string[]; env: Record<string, string> }> = {
|
||||
nanoclaw: {
|
||||
command: 'bun',
|
||||
args: ['run', mcpServerPath],
|
||||
env: {
|
||||
SESSION_INBOUND_DB_PATH: process.env.SESSION_INBOUND_DB_PATH || '/workspace/inbound.db',
|
||||
SESSION_OUTBOUND_DB_PATH: process.env.SESSION_OUTBOUND_DB_PATH || '/workspace/outbound.db',
|
||||
SESSION_HEARTBEAT_PATH: process.env.SESSION_HEARTBEAT_PATH || '/workspace/.heartbeat',
|
||||
},
|
||||
env: {},
|
||||
},
|
||||
};
|
||||
|
||||
// Merge additional MCP servers from host configuration
|
||||
if (process.env.NANOCLAW_MCP_SERVERS) {
|
||||
try {
|
||||
const additional = JSON.parse(process.env.NANOCLAW_MCP_SERVERS) as Record<string, { command: string; args: string[]; env: Record<string, string> }>;
|
||||
for (const [name, config] of Object.entries(additional)) {
|
||||
mcpServers[name] = config;
|
||||
log(`Additional MCP server: ${name} (${config.command})`);
|
||||
}
|
||||
} catch (e) {
|
||||
log(`Failed to parse NANOCLAW_MCP_SERVERS: ${e}`);
|
||||
}
|
||||
for (const [name, serverConfig] of Object.entries(config.mcpServers)) {
|
||||
mcpServers[name] = serverConfig;
|
||||
log(`Additional MCP server: ${name} (${serverConfig.command})`);
|
||||
}
|
||||
|
||||
const provider = createProvider(providerName, {
|
||||
assistantName,
|
||||
assistantName: config.assistantName || undefined,
|
||||
mcpServers,
|
||||
env: { ...process.env },
|
||||
additionalDirectories: additionalDirectories.length > 0 ? additionalDirectories : undefined,
|
||||
@@ -114,7 +91,6 @@ async function main(): Promise<void> {
|
||||
provider,
|
||||
cwd: CWD,
|
||||
systemContext: { instructions },
|
||||
adminUserIds,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user