feat: add setup flow for dedicated research agent
This commit is contained in:
@@ -505,6 +505,7 @@ look up best practices for k8s backup retention
|
|||||||
```
|
```
|
||||||
|
|
||||||
If the `research` agent is not configured, Flynn returns a setup hint with available agent names.
|
If the `research` agent is not configured, Flynn returns a setup hint with available agent names.
|
||||||
|
`flynn setup` can configure this under the Security section.
|
||||||
|
|
||||||
## Running as Service
|
## Running as Service
|
||||||
|
|
||||||
|
|||||||
@@ -486,3 +486,20 @@ hooks:
|
|||||||
# summarize emails, triage inbox items, and prepare quick replies.
|
# summarize emails, triage inbox items, and prepare quick replies.
|
||||||
# Be concise and match the operator's tone. Skip marketing emails.
|
# Be concise and match the operator's tone. Skip marketing emails.
|
||||||
# Never send messages without explicit instruction — draft only.
|
# Never send messages without explicit instruction — draft only.
|
||||||
|
|
||||||
|
# Optional: explicit intent rules for agent routing.
|
||||||
|
# If enabled, these rules are evaluated before default sender/channel routing.
|
||||||
|
# The research agent can already be auto-routed by prefix (`research ...`, `look up ...`)
|
||||||
|
# when agent_configs.research exists and intents are disabled.
|
||||||
|
#
|
||||||
|
# intents:
|
||||||
|
# enabled: true
|
||||||
|
# match_threshold: 0.7
|
||||||
|
# rules:
|
||||||
|
# - name: route-research
|
||||||
|
# patterns: ["research *", "look up *", "lookup *"]
|
||||||
|
# target:
|
||||||
|
# type: agent
|
||||||
|
# name: research
|
||||||
|
# priority: 10
|
||||||
|
# enabled: true
|
||||||
|
|||||||
@@ -3,6 +3,22 @@
|
|||||||
"updated_at": "2026-02-17",
|
"updated_at": "2026-02-17",
|
||||||
"description": "Tracks the status of all Flynn plans and implementation phases",
|
"description": "Tracks the status of all Flynn plans and implementation phases",
|
||||||
"plans": {
|
"plans": {
|
||||||
|
"setup-research-agent-enablement": {
|
||||||
|
"status": "completed",
|
||||||
|
"date": "2026-02-17",
|
||||||
|
"updated": "2026-02-17",
|
||||||
|
"summary": "Extended setup Security flow to provision a dedicated research sub-agent (`agent_configs.research`) with selectable tier, so `/research` and research-prefix auto-routing work immediately after setup. Added builder/test coverage and documented optional explicit intents routing example in default config.",
|
||||||
|
"files_modified": [
|
||||||
|
"src/cli/setup/config.ts",
|
||||||
|
"src/cli/setup/security.ts",
|
||||||
|
"src/cli/setup/config.test.ts",
|
||||||
|
"src/cli/setup/sections.test.ts",
|
||||||
|
"config/default.yaml",
|
||||||
|
"README.md",
|
||||||
|
"docs/plans/state.json"
|
||||||
|
],
|
||||||
|
"test_status": "pnpm test:run src/cli/setup/config.test.ts src/cli/setup/sections.test.ts passing + pnpm typecheck passing"
|
||||||
|
},
|
||||||
"research-prefix-auto-routing": {
|
"research-prefix-auto-routing": {
|
||||||
"status": "completed",
|
"status": "completed",
|
||||||
"date": "2026-02-17",
|
"date": "2026-02-17",
|
||||||
|
|||||||
@@ -85,6 +85,15 @@ describe('ConfigBuilder', () => {
|
|||||||
expect(obj.server.token).toBe('my-secret-token');
|
expect(obj.server.token).toBe('my-secret-token');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('enables research agent config', () => {
|
||||||
|
const builder = new ConfigBuilder();
|
||||||
|
builder.setResearchAgentEnabled({ modelTier: 'complex' });
|
||||||
|
const obj = builder.build();
|
||||||
|
expect(obj.agent_configs?.research?.model_tier).toBe('complex');
|
||||||
|
expect(obj.agent_configs?.research?.tool_profile).toBe('messaging');
|
||||||
|
expect(typeof obj.agent_configs?.research?.system_prompt).toBe('string');
|
||||||
|
});
|
||||||
|
|
||||||
it('applies operator automation pack defaults', () => {
|
it('applies operator automation pack defaults', () => {
|
||||||
const builder = new ConfigBuilder();
|
const builder = new ConfigBuilder();
|
||||||
builder.applyOperatorPack({
|
builder.applyOperatorPack({
|
||||||
|
|||||||
@@ -33,6 +33,16 @@ export interface SetupConfig {
|
|||||||
sandbox?: { enabled?: boolean };
|
sandbox?: { enabled?: boolean };
|
||||||
pairing?: { enabled?: boolean };
|
pairing?: { enabled?: boolean };
|
||||||
tools?: { profile?: string };
|
tools?: { profile?: string };
|
||||||
|
agent_configs?: Record<string, {
|
||||||
|
model_tier?: 'fast' | 'default' | 'complex' | 'local';
|
||||||
|
tool_profile?: string;
|
||||||
|
system_prompt?: string;
|
||||||
|
}>;
|
||||||
|
intents?: {
|
||||||
|
enabled?: boolean;
|
||||||
|
match_threshold?: number;
|
||||||
|
rules?: Array<Record<string, unknown>>;
|
||||||
|
};
|
||||||
automation?: {
|
automation?: {
|
||||||
cron?: Array<Record<string, unknown>>;
|
cron?: Array<Record<string, unknown>>;
|
||||||
webhooks?: Array<Record<string, unknown>>;
|
webhooks?: Array<Record<string, unknown>>;
|
||||||
@@ -62,6 +72,10 @@ interface OperatorPackOptions {
|
|||||||
enableMinioSync?: boolean;
|
enableMinioSync?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ResearchAgentOptions {
|
||||||
|
modelTier: 'fast' | 'default' | 'complex' | 'local';
|
||||||
|
}
|
||||||
|
|
||||||
export class ConfigBuilder {
|
export class ConfigBuilder {
|
||||||
private config: SetupConfig;
|
private config: SetupConfig;
|
||||||
|
|
||||||
@@ -157,6 +171,22 @@ export class ConfigBuilder {
|
|||||||
this.config.tools = { profile };
|
this.config.tools = { profile };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setResearchAgentEnabled(options: ResearchAgentOptions): void {
|
||||||
|
const agentConfigs = (this.config.agent_configs ?? {}) as Record<string, Record<string, unknown>>;
|
||||||
|
const existing = (agentConfigs.research ?? {}) as Record<string, unknown>;
|
||||||
|
agentConfigs.research = {
|
||||||
|
...existing,
|
||||||
|
model_tier: options.modelTier,
|
||||||
|
tool_profile: 'messaging',
|
||||||
|
system_prompt: [
|
||||||
|
'You are a research agent. Find, verify, and synthesize information for the operator.',
|
||||||
|
'Prefer primary sources and include concrete dates and links when available.',
|
||||||
|
'Keep output structured and concise.',
|
||||||
|
].join(' '),
|
||||||
|
};
|
||||||
|
this.config.agent_configs = agentConfigs;
|
||||||
|
}
|
||||||
|
|
||||||
setWebhooksEnabled(secret?: string): void {
|
setWebhooksEnabled(secret?: string): void {
|
||||||
const automation = (this.config.automation ?? {}) as Record<string, unknown>;
|
const automation = (this.config.automation ?? {}) as Record<string, unknown>;
|
||||||
if (secret) {
|
if (secret) {
|
||||||
|
|||||||
@@ -52,14 +52,16 @@ describe('setupMemory', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('setupSecurity', () => {
|
describe('setupSecurity', () => {
|
||||||
it('enables sandbox and pairing', async () => {
|
it('enables sandbox, pairing, and research agent', async () => {
|
||||||
const rl = mockReadline(['y', 'y', '']);
|
const rl = mockReadline(['y', 'y', '', '', '']);
|
||||||
const p = createPrompter(rl);
|
const p = createPrompter(rl);
|
||||||
const builder = new ConfigBuilder();
|
const builder = new ConfigBuilder();
|
||||||
await setupSecurity(p, builder);
|
await setupSecurity(p, builder);
|
||||||
const config = builder.build();
|
const config = builder.build();
|
||||||
expect(config.sandbox!.enabled).toBe(true);
|
expect(config.sandbox!.enabled).toBe(true);
|
||||||
expect(config.pairing!.enabled).toBe(true);
|
expect(config.pairing!.enabled).toBe(true);
|
||||||
|
expect(config.agent_configs?.research?.model_tier).toBe('complex');
|
||||||
|
expect(config.agent_configs?.research?.tool_profile).toBe('messaging');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,13 @@ const TOOL_PROFILES = [
|
|||||||
{ label: 'full (unrestricted)', value: 'full' },
|
{ label: 'full (unrestricted)', value: 'full' },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const RESEARCH_AGENT_TIERS = [
|
||||||
|
{ label: 'complex (recommended)', value: 'complex' as const },
|
||||||
|
{ label: 'default', value: 'default' as const },
|
||||||
|
{ label: 'fast', value: 'fast' as const },
|
||||||
|
{ label: 'local', value: 'local' as const },
|
||||||
|
];
|
||||||
|
|
||||||
export async function setupSecurity(p: Prompter, builder: ConfigBuilder): Promise<void> {
|
export async function setupSecurity(p: Prompter, builder: ConfigBuilder): Promise<void> {
|
||||||
p.println(' Docker sandboxing runs tool commands in isolated containers.');
|
p.println(' Docker sandboxing runs tool commands in isolated containers.');
|
||||||
p.println(' Requires Docker installed and running.');
|
p.println(' Requires Docker installed and running.');
|
||||||
@@ -34,4 +41,14 @@ export async function setupSecurity(p: Prompter, builder: ConfigBuilder): Promis
|
|||||||
p.println(' minimal — status checks only (read-only, safest)');
|
p.println(' minimal — status checks only (read-only, safest)');
|
||||||
const profile = await p.choose('Tool policy profile:', TOOL_PROFILES);
|
const profile = await p.choose('Tool policy profile:', TOOL_PROFILES);
|
||||||
builder.setToolProfile(profile);
|
builder.setToolProfile(profile);
|
||||||
|
|
||||||
|
p.println();
|
||||||
|
p.println(' Research agent adds a dedicated specialist for deep web research.');
|
||||||
|
p.println(' Enables /research command and automatic routing for messages starting with "research ..." or "look up ...".');
|
||||||
|
const enableResearchAgent = await p.confirm('Enable a dedicated research agent?', true);
|
||||||
|
if (enableResearchAgent) {
|
||||||
|
const tier = await p.choose('Research agent model tier:', RESEARCH_AGENT_TIERS);
|
||||||
|
builder.setResearchAgentEnabled({ modelTier: tier });
|
||||||
|
p.println(`✓ Research agent enabled (tier=${tier})`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user