diff --git a/a2as.yaml b/a2as.yaml new file mode 100644 index 0000000..a719a5e --- /dev/null +++ b/a2as.yaml @@ -0,0 +1,496 @@ +manifest: + version: "0.1.3" + schema: https://a2as.org/cert/schema + subject: + name: magnieet/leadgenerationagentadk + source: https://github.com/magnieet/leadgenerationagentadk + branch: main + commit: "ccdc996f" + scope: [LeadGenerationResearch/sub_agents/intent_extractor/agent.py, LeadGenerationResearch/sub_agents/lead_generation/schemas.py, + LeadGenerationResearch/tools/agent_tools.py, LeadGenerationResearch/tools/callbacks.py, LeadGenerationResearch/sub_agents/lead_generation/prompt.py, + LeadGenerationResearch/prompt.py, LeadGenerationResearch/sub_agents/intent_extractor/schemas.py, LeadGenerationResearch/sub_agents/pattern_discovery/agent.py, + LeadGenerationResearch/sub_agents/pattern_discovery/prompt.py, LeadGenerationResearch/sub_agents/intent_extractor/prompt.py, + LeadGenerationResearch/sub_agents/lead_generation/agent.py, LeadGenerationResearch/agent.py, LeadGenerationResearch/sub_agents/pattern_discovery/schemas.py, + deployment/deploy.py] + issued: + by: A2AS.org + at: '2026-02-11T16:33:52Z' + url: https://a2as.org/certified/agents/magnieet/leadgenerationagentadk + signatures: + digest: sha256:vpdCpCEmYtFoaZTAvtm432mCWj_GKXpvaVG0V4Fqkjc + key: ed25519:omMmLuT7Pgbf1nbrizuqvwkyqE8rv9XDZLC8Iu58-9o + sig: ed25519:3_HMgNhheyWYSTwLmktJeD6_o6Y3N3NbgS2DGaHmj7WzCLFTOM0qutY9OhlM6_jbPZsInrcWiZyIsNiBSGCoDA + +agents: + company_finder_agent: + type: instance + models: [gemini-2.5-pro] + tools: [google_search] + params: + name: CompanyFinderAgent + instruction: [You are a Company Finder Agent. Your mission is to find a specific number of international companies that + have recently and successfully invested in a target market., '**CRITICAL INSTRUCTIONS:**', '1. **Use the Google + Search tool** to find real, verifiable information. Do not invent companies.', 3. **Prioritize RECENT investments** + (within the last 2-3 years)., '4. **Your final output MUST be a single, valid JSON object and NOTHING ELSE.** Do + not include any introductory text, explanations, or markdown formatting like ```json.', '5. The JSON object must + have a single key: "companies_found".', 6. The value of "companies_found" must be a list of company objects., '7. Each + company object in the list must have the following keys: "company_name", "country_of_origin", "investment_type", + "investment_date", "source_url", "business_description".', '**Target Market:**', '* **Country:** {country}', '* **Industry:** + {industry}', '* **Number of companies to find (`k`):** {k}', '**Search Strategy:**', '* Find news articles, + press releases, or official company announcements about market entry.'] + output_key: company_finder_output + company_formatter_agent: + type: instance + models: [gemini-2.0-flash] + params: + name: CompanyFormatterAgent + instruction: [You are a data formatting agent. Your only job is to take the unstructured text provided below and convert + it into a valid JSON object that conforms to the `CompanyFinderOutput` schema., '**CRITICAL INSTRUCTIONS:**', '1. Read + the unstructured text, which contains information about companies.', 2. Extract the information for each company., + '3. Your final output MUST be a single, valid JSON object and NOTHING ELSE. Do not include any introductory text, + explanations, or markdown formatting.', '4. The JSON object must have a single key: "companies_found".', 5. The + value of "companies_found" must be a list of company objects., '6. Each company object in the list must have the + following keys: "company_name", "country_of_origin", "investment_type", "investment_date", "source_url", "business_description".', + '**Unstructured Text to Format:**', '{company_finder_output}'] + output_schema: CompanyFinderOutput + output_key: companies_found_structured + description: Takes unstructured text about companies and formats it into a valid JSON object. + intent_extractor_agent: + type: instance + models: [gemini-2.0-flash] + params: + name: intent_extractor_agent + instruction: [You are an Intent Extraction Agent for a lead generation system. Your job is to analyze user queries and + conversation context to extract key information and determine the appropriate action., '**CURRENT SESSION STATE:**', + '- Previous Country: {country}', '- Previous Industry: {industry}', '- Current Stage: {stage}', '**Your Task:**', + 'Analyze the user''s latest message to extract:', 1. **Country** - Target country for lead generation, 2. **Industry** + - Target industry sector, 3. **Stage** - What the user wants to do next, 4. **Intent** - User's primary goal, '**Stage + Determination Logic:**', '- **pattern_discovery**: User wants to find patterns/signals (first time or new search)', + '- **lead_generation**: User wants to find actual leads (after seeing patterns)', '- **follow_up**: User has follow-up + questions about existing results', '- **chitchat**: General conversation, greetings, off-topic', '**Intent Categories:**', + '- **find_leads**: User wants to find companies/leads/prospects', '- **find_patterns**: User wants to understand investment + patterns/signals', '- **company_research**: User wants research on specific companies', '- **general_chat**: Casual + conversation', '**Context-Aware Extraction:**', '- If country/industry mentioned previously, consider carrying them + forward', '- If user says "find leads" after seeing patterns, set stage to "lead_generation"', '- If user asks about + specific companies, consider "company_research" intent', '- Be smart about partial information - if industry mentioned + before, keep it', '**Examples:**', '**User**: "Find fintech companies expanding into Thailand"', '→ Country: "Thailand", + Industry: "fintech", Stage: "pattern_discovery", Intent: "find_leads"', '**User**: "Find leads" (after patterns + were shown)', '→ Keep previous country/industry, Stage: "lead_generation", Intent: "find_leads"', '**User**: "What + signals do successful companies show in Singapore?"', '→ Country: "Singapore", Industry: carry forward or extract, + Stage: "pattern_discovery", Intent: "find_patterns"', '**User**: "Tell me more about Grab''s expansion"', '→ Keep + context, Stage: "follow_up", Intent: "company_research"', '**User**: "Hello, how are you?"', '→ Stage: "chitchat", + Intent: "general_chat"', '**OUTPUT FORMAT - CRITICAL RULES:**', '- You MUST return ONLY valid JSON.', '- The `reasoning` + field MUST be a single, short sentence and under 15 words.', '```json', '{', '"country": "Thailand",', '"industry": + "fintech",', '"stage": "pattern_discovery",', '"intent": "find_leads",', '"confidence": 0.9,', '"reasoning": "User + requested fintech leads in Thailand."', '}', '```', '**Special Handling:**', '- If country/industry missing but + available in context, use context values', '- If user says "yes" or "find leads" after patterns shown, set stage + to "lead_generation"', '- If conversation is off-topic, set stage to "chitchat"', '- Your reasoning MUST be a single, + short sentence under 15 words.', '**Current Time**: {current_time}', Analyze the conversation and return ONLY the + JSON response.] + output_schema: IntentExtractionResult + output_key: intent_extraction_result + description: Extracts user intent, country, industry, and conversation stage from queries + lead_finder_agent: + type: instance + models: [gemini-2.5-pro] + tools: [google_search] + params: + name: LeadFinderAgent + instruction: ['**CRITICAL INSTRUCTIONS:**', '1. **Use the Google Search tool** to find real, verifiable information.', + 2. **Use the discovered patterns** below as the basis for your search. Look for companies showing these specific + signals., 4. **Return your findings as unstructured text.** Another agent will be responsible for formatting it., + '**Discovered Patterns to Search For:**', '{discovered_patterns}', '**Target Market:**', '* **Country:** {country}', + '* **Industry:** {industry}'] + output_key: lead_finder_output + lead_formatter_agent: + type: instance + models: [gemini-2.0-flash] + params: + name: LeadFormatterAgent + instruction: [Format the unstructured text from the Lead Finder into the `LeadFinderOutput` JSON schema.] + output_schema: LeadFinderOutput + output_key: leads_found_structured + lead_generation_agent: + type: instance + params: + name: LeadGenerationAgent + description: Orchestrates the workflow for finding and qualifying new investment leads. + sub_agents: [lead_finder_agent, lead_formatter_agent, lead_research_orchestrator_agent, report_orchestrator_agent, report_compiler_agent] + lead_research_orchestrator_agent: + type: instance + models: [gemini-2.5-flash] + params: + name: LeadResearchOrchestrator + lead_signal_analyzer_template: + type: instance + models: [gemini-2.5-pro] + tools: [google_search] + params: + name: LeadSignalAnalyzerAgent + instruction: ['You are a Lead Signal Analyzer Agent. Your job is to analyze a single, validated company and identify + the specific pre-investment signals it is showing.', '**CRITICAL INSTRUCTIONS:**', 1. **Use the Google Search tool** + to find recent news and activities related to the company., 2. **Compare the company's activities** to the list + of known pre-investment patterns., 3. **Identify which specific signals** the company is exhibiting., 4. **You + MUST find and include the source URLs** for all signals you identify., 5. **Your final output MUST be a valid JSON + object** that conforms to the `LeadSignalAnalyzerOutput` schema., '**Company to Analyze:**', '{company_data}', '**Known + Pre-Investment Patterns:**', '{discovered_patterns}'] + output_key: lead_signal_analyzer_output + lead_signal_formatter_template: + type: instance + models: [gemini-2.0-flash] + params: + name: LeadSignalFormatterAgent + instruction: [Format the following unstructured text from the Lead Signal Analyzer into the `LeadSignalAnalyzerOutput` + JSON schema., '{unstructured_text}'] + output_schema: LeadSignalAnalyzerOutput + output_key: lead_analysis_findings + parallel_researcher.0: + type: instance + params: + name: DynamicParallelResearcher + function: _run_async_impl + sub_agents: [research_pipelines] + instance: parallel_researcher + parallel_researcher.1: + type: instance + params: + name: DynamicLeadResearcher + function: _run_async_impl + instance: parallel_researcher + sub_agents: [research_pipelines] + pattern_discovery_agent: + type: instance + params: + name: PatternDiscoveryAgent + description: Orchestrates the technical steps of discovering investment patterns. + sub_agents: [company_finder_agent, company_formatter_agent, research_orchestrator_agent, synthesizer_orchestrator_agent, + pattern_synthesizer_agent] + pattern_synthesizer_agent: + type: instance + models: [gemini-2.5-pro] + params: + name: PatternSynthesizerAgent + instruction: [You are a Pattern Synthesizer Agent. Your job is to analyze the research findings from multiple Signal + Searcher Agents and identify the common patterns., '**CRITICAL INSTRUCTIONS:**', '1. **Review the consolidated + research summary** provided below. This summary contains the validation status, research findings, and sources for + each company.', 2. **Only synthesize patterns from companies that were successfully validated.**, 3. **Identify + the common themes and patterns** across the valid companies., '4. **For each pattern, you MUST cite the source + URLs** from the research that support it.', '5. **Your final output** should be a clear, human-readable summary + of the discovered patterns, with citations for each pattern.', '**Consolidated Research Summary:**', '{all_research_findings}'] + output_key: discovered_patterns + description: Analyzes research from multiple signal searchers and synthesizes the common patterns. + report_compiler_agent: + type: instance + models: [gemini-2.5-pro] + params: + name: ReportCompilerAgent + instruction: ['You are a Report Compiler Agent. Your job is to take the results of the parallel validation and signal + analysis and compile them into a single, clean, human-readable report.', '**CRITICAL INSTRUCTIONS:**', 1. **Review + the consolidated research summary** provided below. This summary contains the analysis and sources for each potential + lead., '2. **Synthesize this information** into a single, clean, human-readable report.', '3. **For each company, + clearly list the company name, the analysis summary, and the supporting sources.**', '4. **Format your final output** + as a clear, human-readable markdown list, highlighting the key signals and their sources for each company.', '**Consolidated + Research Summary:**', '{all_lead_findings}'] + report_orchestrator_agent: + type: instance + models: [gemini-2.5-flash] + params: + name: ReportOrchestrator + research_orchestrator_agent: + type: instance + models: [gemini-2.5-flash] + params: + name: ResearchOrchestrator + research_pipeline.0: + type: instance + params: + name: CompanyResearchPipeline_{i} + function: _run_async_impl + sub_agents: ['ValidatorAgent_{i}', 'SignalSearcher_{i}', 'SignalFormatter_{i}'] + instance: research_pipeline + research_pipeline.1: + type: instance + params: + name: LeadResearchPipeline_{i} + function: _run_async_impl + instance: research_pipeline + sub_agents: ['LeadValidator_{i}', 'LeadSignalAnalyzer_{i}', 'LeadSignalFormatter_{i}'] + root_agent: + type: instance + models: [gemini-2.5-pro] + tools: [get_user_choice] + params: + name: InteractiveLeadGenerator + instruction: [You are the lead generation assistant. Your goal is to help the user find new leads by discovering patterns + in successful companies., '**SESSION STATE:**', '- `country`: The target country for lead generation.', '- `industry`: + The target industry for lead generation.', '- `k`: The number of companies to analyze.', '- `m`: The number of leads + to find.', '- `stage`: The current stage of the conversation. Can be `initial`, `pattern_discovery`, `lead_generation`, + `patterns_shown`, `follow_up`, or `chitchat`.', '**FLOW:**', '1. **Initial Stage:**', '- First, you MUST call + the `intent_extractor_agent` to determine the user''s intent, country, and industry. This will update the `stage` + in the session state.', '2. **Pattern Discovery Stage (`stage == "pattern_discovery"`):**', '- If `k` is not + in the session state, you MUST call the `get_user_choice` tool to ask the user how many companies they want to analyze. + The options MUST be `["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]`. The `context` for this call MUST be `"set_k_for_patterns"`.', + '- Once you have `k`, you MUST call the `pattern_discovery_agent`. This agent will first find companies, then validate + them in parallel.', '3. **Patterns Shown Stage (`stage == "patterns_shown"`):**', '- After the patterns have + been discovered and shown to the user, you MUST call the `get_user_choice` tool to ask the user if they want to + proceed with lead generation. The options MUST be `["Yes, find leads", "No, start over"]`. The `context` for this + call MUST be `"confirm_lead_generation"`.', '4. **Lead Generation Stage (`stage == "lead_generation"`):**', '- This + stage is entered after the user has confirmed they want to proceed, or if they ask for leads directly.', '- **CRITICAL:** + Before generating leads, you MUST have discovered patterns. If `discovered_patterns` is not in the session state, + you MUST perform the **Pattern Discovery Stage** steps first.', '- If `discovered_patterns` is in the session + state:', '- If `m` is not in the session state, you MUST call the `get_user_choice` tool to ask the user how many + leads they want to find. The options MUST be `["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]`. The `context` + for this call MUST be `"set_m_for_leads"`.', '- Once you have `m`, you MUST call the `lead_generation_agent` to + find new leads based on the discovered patterns.', '5. **Chit-Chat Stage (`stage == "chitchat"`):**', '- If the + user is just making small talk, respond politely and guide them back to lead generation.'] + before_agent_callback: [before_agent_run] + after_tool_callback: [after_tool_run] + signal_formatter_agent_template: + type: instance + models: [gemini-2.0-flash] + params: + name: SignalFormatterAgent + instruction: [Format the following unstructured text from the Signal Searcher into the `SignalSearcherOutput` JSON schema., + '{unstructured_text}'] + output_schema: SignalSearcherOutput + output_key: research_findings + signal_searcher_agent_template: + type: instance + models: [gemini-2.5-pro] + tools: [google_search] + params: + name: SignalSearcherAgent + instruction: ['**CRITICAL INSTRUCTIONS:**', '1. **Use the Google Search tool** to find real, verifiable information.', + 2. **Focus your research** on the 6-18 month period *before* the company's investment date., '3. **Look for specific + signal categories:** Executive hiring, market research, financial preparation, operational groundwork, and public + signaling.', 4. **You MUST find and include the source URLs** for all claims you make., 5. **Your final output + MUST be a valid JSON object** that conforms to the `SignalSearcherOutput` schema., '**Company to Research:**', '{company_data}'] + output_key: signal_searcher_output + description: Researches a single, validated company to find its pre-investment signals. + synthesizer_orchestrator_agent: + type: instance + models: [gemini-2.5-flash] + params: + name: SynthesizerOrchestrator + validator_agent_template: + type: instance + models: [gemini-2.0-flash] + params: + name: ValidatorAgent + instruction: [You are a meticulous Validation Agent. Your job is to verify if a given company meets a set of strict + criteria based on the provided information and by using Google Search to confirm the details., '**CRITICAL VALIDATION + CRITERIA:**', '4. **Source Must be Verifiable:** You MUST visit the "source_url" to confirm that the information + is accurate and supports the investment claim.', '**INPUT DATA (A single company):**', '```json', '{company_to_validate}', + '```', '**YOUR TASK:**', 1. Carefully review the input data for the company., '2. Use Google Search to verify the + information, especially the country of origin and the investment date, using the provided source URL and other searches + if necessary.', '3. Based on your verification, determine if the company is valid according to ALL the criteria + above.', 4. Provide a clear "True" or "False" for `is_valid` and a concise `reasoning` for your decision., '5. If + you discover the correct country of origin is different from what was provided, correct it in the `corrected_country_of_origin` + field.', '**FINAL OUTPUT (JSON ONLY):**', Return ONLY a valid JSON object with the exact structure of the `ValidationResult` + schema. No extra text or explanations.] + output_schema: ValidationResult + description: Validates a single company to ensure it is a foreign entity that has recently invested in the target market. + +models: + gemini-2.0-flash: + type: literal + agents: [intent_extractor_agent, company_formatter_agent, validator_agent_template, signal_formatter_agent_template, lead_formatter_agent, + lead_signal_formatter_template] + gemini-2.5-flash: + type: default + agents: [synthesizer_orchestrator_agent, research_orchestrator_agent, report_orchestrator_agent, lead_research_orchestrator_agent] + gemini-2.5-pro: + type: literal + agents: [company_finder_agent, signal_searcher_agent_template, pattern_synthesizer_agent, lead_finder_agent, lead_signal_analyzer_template, + report_compiler_agent, root_agent] + lead_signal_analyzer_template.model: + type: literal + agents: ['LeadSignalAnalyzer_{i}'] + lead_signal_formatter_template.model: + type: literal + agents: ['LeadSignalFormatter_{i}'] + signal_formatter_agent_template.model: + type: literal + agents: ['SignalFormatter_{i}'] + signal_searcher_agent_template.model: + type: literal + agents: ['SignalSearcher_{i}'] + validator_agent_template.model: + type: literal + agents: ['ValidatorAgent_{i}', 'LeadValidator_{i}'] + +tools: + get_user_choice: + type: function + agents: [root_agent] + google_search: + type: module + agents: [company_finder_agent, signal_searcher_agent_template, lead_finder_agent, lead_signal_analyzer_template] + lead_signal_analyzer_template.tools: + type: function + params: + dynamic: "True" + signal_searcher_agent_template.tools: + type: function + params: + dynamic: "True" + +teams: + lead_generation_agent: + type: sequential + agents: [lead_generation_agent, lead_finder_agent, lead_formatter_agent, lead_research_orchestrator_agent, report_orchestrator_agent, + report_compiler_agent] + parallel_researcher: + type: parallel + agents: [parallel_researcher, research_pipelines] + parallel_researcher.1: + type: parallel + agents: [parallel_researcher.1, research_pipelines] + pattern_discovery_agent: + type: sequential + agents: [pattern_discovery_agent, company_finder_agent, company_formatter_agent, research_orchestrator_agent, synthesizer_orchestrator_agent, + pattern_synthesizer_agent] + research_pipeline: + type: sequential + agents: [research_pipeline, 'ValidatorAgent_{i}', 'SignalSearcher_{i}', 'SignalFormatter_{i}'] + research_pipeline.1: + type: sequential + agents: [research_pipeline.1, 'LeadValidator_{i}', 'LeadSignalAnalyzer_{i}', 'LeadSignalFormatter_{i}'] + +imports: + AdkApp: vertexai.preview.reasoning_engines.AdkApp + after_tool_run: tools.callbacks.after_tool_run + Agent: google.adk.agents.Agent + agent_engines: vertexai.agent_engines + agent_tools: tools.agent_tools.agent_tools + AgentTool: google.adk.tools.agent_tool.AgentTool + app: absl.app + AsyncGenerator: typing.AsyncGenerator + BaseAgent: google.adk.agents.BaseAgent + BaseModel: pydantic.BaseModel + before_agent_run: tools.callbacks.before_agent_run + CallbackContext: google.adk.agents.callback_context.CallbackContext + company_finder_agent: sub_agents.pattern_discovery.agent.company_finder_agent + COMPANY_FINDER_PROMPT: prompt.COMPANY_FINDER_PROMPT + CompanyFinderOutput: schemas.CompanyFinderOutput + Content: google.genai.types.Content + datetime: datetime.datetime + Event: google.adk.events.Event + Field: pydantic.Field + flags: absl.flags + FORMATTER_PROMPT: prompt.FORMATTER_PROMPT + get_user_choice: google.adk.tools.get_user_choice + google_search: google.adk.tools.google_search + intent_extractor_agent: sub_agents.intent_extractor.agent.intent_extractor_agent + INTENT_EXTRACTOR_PROMPT: prompt.INTENT_EXTRACTOR_PROMPT + IntentExtractionResult: sub_agents.intent_extractor.agent.IntentExtractionResult + InvocationContext: google.adk.agents.invocation_context.InvocationContext + LEAD_FINDER_PROMPT: prompt.LEAD_FINDER_PROMPT + lead_generation_agent: sub_agents.lead_generation.agent.lead_generation_agent + LEAD_SIGNAL_ANALYZER_PROMPT: prompt.LEAD_SIGNAL_ANALYZER_PROMPT + LeadFinderOutput: schemas.LeadFinderOutput + LeadSignalAnalyzerOutput: schemas.LeadSignalAnalyzerOutput + List: typing.List + Literal: typing.Literal + LlmAgent: google.adk.agents.LlmAgent + load_dotenv: dotenv.load_dotenv + Optional: typing.Optional + os: os + ParallelAgent: google.adk.agents.ParallelAgent + Part: google.genai.types.Part + pattern_discovery_agent: sub_agents.pattern_discovery.agent.pattern_discovery_agent + REPORT_COMPILER_PROMPT: prompt.REPORT_COMPILER_PROMPT + root_agent: LeadGenerationResearch.agent.root_agent + ROOT_AGENT_INSTRUCTION: prompt.ROOT_AGENT_INSTRUCTION + SequentialAgent: google.adk.agents.SequentialAgent + SIGNAL_SEARCHER_PROMPT: prompt.SIGNAL_SEARCHER_PROMPT + SignalSearcherOutput: schemas.SignalSearcherOutput + SYNTHESIZER_PROMPT: prompt.SYNTHESIZER_PROMPT + sys: sys + ToolContext: google.adk.tools.tool_context.ToolContext + ValidationResult: schemas.ValidationResult + validator_agent_template: pattern_discovery.agent.validator_agent_template + VALIDATOR_PROMPT: prompt.VALIDATOR_PROMPT + vertexai: vertexai + +functions: + _run_async_impl: + type: async + module: LeadGenerationResearch.sub_agents.lead_generation.agent + args: [self, ctx] + params: + returns: AsyncGenerator + after_tool_run: + type: sync + module: LeadGenerationResearch.tools.callbacks + args: [tool, args, tool_context, tool_response] + params: + returns: None + before_agent_run: + type: sync + module: LeadGenerationResearch.tools.callbacks + args: [callback_context] + params: + returns: Optional[Content] + create: + type: sync + module: deployment.deploy + params: + returns: None + delete: + type: sync + module: deployment.deploy + args: [resource_id] + params: + returns: None + list_agents: + type: sync + module: deployment.deploy + params: + returns: None + main: + type: sync + module: deployment.deploy + args: [argv] + params: + returns: None + +variables: + GEN_ADVANCED_MODEL: + type: env + params: + caller: [os.getenv] + path: [deployment.deploy] + GEN_FAST_MODEL: + type: env + params: + caller: [os.getenv] + path: [deployment.deploy] + GOOGLE_CLOUD_LOCATION: + type: env + params: + caller: [os.getenv] + path: [deployment.deploy] + GOOGLE_CLOUD_PROJECT: + type: env + params: + caller: [os.getenv] + path: [deployment.deploy] + GOOGLE_CLOUD_STORAGE_BUCKET: + type: env + params: + caller: [os.getenv] + path: [deployment.deploy] + GOOGLE_GENAI_USE_VERTEXAI: + type: env + params: + caller: [os.getenv] + path: [deployment.deploy] + +files: + "..": + type: pattern + actions: [read] + params: + caller: [os.path.join] + pattern: [os.path.dirname(), ".."]