From be993146c08abe10f3e9d5edc8ceea725fd2ee7d Mon Sep 17 00:00:00 2001 From: William Valentin Date: Tue, 17 Feb 2026 15:27:09 -0800 Subject: [PATCH] feat: add setup flow for dedicated research agent --- README.md | 1 + config/default.yaml | 17 +++++++++++++++++ docs/plans/state.json | 16 ++++++++++++++++ src/cli/setup/config.test.ts | 9 +++++++++ src/cli/setup/config.ts | 30 ++++++++++++++++++++++++++++++ src/cli/setup/sections.test.ts | 6 ++++-- src/cli/setup/security.ts | 17 +++++++++++++++++ 7 files changed, 94 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 11adc5d..f5b4b1e 100644 --- a/README.md +++ b/README.md @@ -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. +`flynn setup` can configure this under the Security section. ## Running as Service diff --git a/config/default.yaml b/config/default.yaml index f082607..ad6754d 100644 --- a/config/default.yaml +++ b/config/default.yaml @@ -486,3 +486,20 @@ hooks: # summarize emails, triage inbox items, and prepare quick replies. # Be concise and match the operator's tone. Skip marketing emails. # 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 diff --git a/docs/plans/state.json b/docs/plans/state.json index 1868fa0..8955199 100644 --- a/docs/plans/state.json +++ b/docs/plans/state.json @@ -3,6 +3,22 @@ "updated_at": "2026-02-17", "description": "Tracks the status of all Flynn plans and implementation phases", "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": { "status": "completed", "date": "2026-02-17", diff --git a/src/cli/setup/config.test.ts b/src/cli/setup/config.test.ts index 23ba60d..aa8ec48 100644 --- a/src/cli/setup/config.test.ts +++ b/src/cli/setup/config.test.ts @@ -85,6 +85,15 @@ describe('ConfigBuilder', () => { 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', () => { const builder = new ConfigBuilder(); builder.applyOperatorPack({ diff --git a/src/cli/setup/config.ts b/src/cli/setup/config.ts index 80f3c52..af5b93f 100644 --- a/src/cli/setup/config.ts +++ b/src/cli/setup/config.ts @@ -33,6 +33,16 @@ export interface SetupConfig { sandbox?: { enabled?: boolean }; pairing?: { enabled?: boolean }; tools?: { profile?: string }; + agent_configs?: Record; + intents?: { + enabled?: boolean; + match_threshold?: number; + rules?: Array>; + }; automation?: { cron?: Array>; webhooks?: Array>; @@ -62,6 +72,10 @@ interface OperatorPackOptions { enableMinioSync?: boolean; } +interface ResearchAgentOptions { + modelTier: 'fast' | 'default' | 'complex' | 'local'; +} + export class ConfigBuilder { private config: SetupConfig; @@ -157,6 +171,22 @@ export class ConfigBuilder { this.config.tools = { profile }; } + setResearchAgentEnabled(options: ResearchAgentOptions): void { + const agentConfigs = (this.config.agent_configs ?? {}) as Record>; + const existing = (agentConfigs.research ?? {}) as Record; + 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 { const automation = (this.config.automation ?? {}) as Record; if (secret) { diff --git a/src/cli/setup/sections.test.ts b/src/cli/setup/sections.test.ts index 4c2fb09..70e5197 100644 --- a/src/cli/setup/sections.test.ts +++ b/src/cli/setup/sections.test.ts @@ -52,14 +52,16 @@ describe('setupMemory', () => { }); describe('setupSecurity', () => { - it('enables sandbox and pairing', async () => { - const rl = mockReadline(['y', 'y', '']); + it('enables sandbox, pairing, and research agent', async () => { + const rl = mockReadline(['y', 'y', '', '', '']); const p = createPrompter(rl); const builder = new ConfigBuilder(); await setupSecurity(p, builder); const config = builder.build(); expect(config.sandbox!.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'); }); }); diff --git a/src/cli/setup/security.ts b/src/cli/setup/security.ts index ed3fa55..b70eeef 100644 --- a/src/cli/setup/security.ts +++ b/src/cli/setup/security.ts @@ -8,6 +8,13 @@ const TOOL_PROFILES = [ { 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 { p.println(' Docker sandboxing runs tool commands in isolated containers.'); 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)'); const profile = await p.choose('Tool policy profile:', TOOL_PROFILES); 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})`); + } }