Compare commits

...

7 Commits

Author SHA1 Message Date
gavrielc 4a3e01cab8 Merge branch 'main' into feat/lean-harness-defaults 2026-07-13 16:32:28 +03:00
gavrielc b52c8c6dca Update src/group-init.settings.test.ts 2026-07-13 16:32:00 +03:00
gavrielc 69913f23ec Update CHANGELOG.md 2026-07-13 16:31:48 +03:00
gavrielc 1de7dba2b7 Update src/group-init.ts 2026-07-13 16:31:40 +03:00
gavrielc 37f60c831d Update src/group-init.ts 2026-07-13 16:31:27 +03:00
gavrielc 6bf3b3929e Update src/group-init.settings.test.ts 2026-07-13 16:31:16 +03:00
Gabi Simons 4f5440d2ab feat: lean harness defaults for new agent groups
New groups' settings.json no longer enables Claude Code's experimental
agent-teams feature and sets disableWorkflows: both overlap NanoClaw's
own systems (a2a messaging; host-side orchestration), and together they
are ~20% of every turn's context (live-measured: ~38,900 -> ~30,300
tokens on a fresh session). DesignSync and ReportFindings — desktop/UI
tools that cannot function headless — join the fixed disallow list.

Existing groups are untouched: their settings.json already exists and
is only ever patched additively, never regenerated. Re-enabling either
feature for one group is a one-file edit to that group's settings.json
plus a group restart — and it sticks, because nothing reconciles the
file.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 09:52:13 +03:00
3 changed files with 74 additions and 1 deletions
@@ -24,6 +24,10 @@ function log(msg: string): void {
// the question and blocks on the real reply.
// - EnterPlanMode / ExitPlanMode / EnterWorktree / ExitWorktree: Claude
// Code UI affordances; in a headless container they'd appear stuck.
// - DesignSync: desktop design-tool integration — nothing to sync with in a
// headless container (~9.3KB/turn schema).
// - ReportFindings: code-review-reporting UI affordance with no headless
// host surface to receive it (~1.9KB/turn schema).
const SDK_DISALLOWED_TOOLS = [
'CronCreate',
'CronDelete',
@@ -34,6 +38,8 @@ const SDK_DISALLOWED_TOOLS = [
'ExitPlanMode',
'EnterWorktree',
'ExitWorktree',
'DesignSync',
'ReportFindings',
];
// Tool allowlist for NanoClaw agent containers. MCP-tool entries are derived
+68
View File
@@ -0,0 +1,68 @@
import fs from 'fs';
import path from 'path';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
const TEST_ROOT = '/tmp/nanoclaw-group-init-settings-test';
vi.mock('./config.js', async (importOriginal) => ({
...(await importOriginal<typeof import('./config.js')>()),
DATA_DIR: '/tmp/nanoclaw-group-init-settings-test/data',
GROUPS_DIR: '/tmp/nanoclaw-group-init-settings-test/groups',
}));
vi.mock('./log.js', () => ({
log: { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn(), fatal: vi.fn() },
}));
import { closeDb, createAgentGroup, initTestDb, runMigrations } from './db/index.js';
import { initGroupFilesystem } from './group-init.js';
import type { AgentGroup } from './types.js';
function makeGroup(id: string): AgentGroup {
const ag = { id, name: id, folder: id, agent_provider: null, created_at: new Date().toISOString() } as AgentGroup;
createAgentGroup(ag);
return ag;
}
beforeEach(() => {
fs.rmSync(TEST_ROOT, { recursive: true, force: true });
fs.mkdirSync(TEST_ROOT, { recursive: true });
runMigrations(initTestDb());
});
afterEach(() => {
closeDb();
fs.rmSync(TEST_ROOT, { recursive: true, force: true });
});
describe('default settings.json for new groups', () => {
it('is lean: no agent-teams env key, unmanaged keys intact', () => {
const ag = makeGroup('ag-lean');
initGroupFilesystem(ag, {});
const file = path.join(TEST_ROOT, 'data', 'v2-sessions', ag.id, '.claude-shared', 'settings.json');
const settings = JSON.parse(fs.readFileSync(file, 'utf-8'));
expect(settings.env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS).toBeUndefined();
expect(settings.env.CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD).toBe('1');
expect(JSON.stringify(settings.hooks.PreCompact)).toContain('compact-instructions');
});
it('never rewrites an existing settings.json — a hand-edited re-enable sticks', () => {
const ag = makeGroup('ag-reenable');
initGroupFilesystem(ag, {});
const file = path.join(TEST_ROOT, 'data', 'v2-sessions', ag.id, '.claude-shared', 'settings.json');
// Operator re-enables both features by editing the file (the documented path).
const edited = JSON.parse(fs.readFileSync(file, 'utf-8'));
delete edited.disableWorkflows;
edited.env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS = '1';
fs.writeFileSync(file, JSON.stringify(edited, null, 2) + '\n');
initGroupFilesystem(ag, {}); // next spawn
const after = JSON.parse(fs.readFileSync(file, 'utf-8'));
expect(after.disableWorkflows).toBeUndefined();
expect(after.env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS).toBe('1');
});
});
-1
View File
@@ -11,7 +11,6 @@ const DEFAULT_SETTINGS_JSON =
JSON.stringify(
{
env: {
CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: '1',
CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD: '1',
CLAUDE_CODE_DISABLE_AUTO_MEMORY: '0',
},