---
id: "03-01"
phase: 03-miroflow-embutido
plan: 01
type: execute
wave: 0
depends_on: []
files_modified:
  - server/python/miroflow_service.py
  - server/python/test_miroflow_service.py
autonomous: true
requirements:
  - REQ-3.1
  - REQ-3.2
  - REQ-3.3
  - REQ-3.4

must_haves:
  truths:
    - "ollama pull llama3.1:8b completa sem erro"
    - "miroflow_service.py sobe na porta 8006 sem ModuleNotFoundError"
    - "GET http://localhost:8006/health retorna {status: ok}"
    - "Agente statistician instanciado com deepseek-r1:14b via UnifiedOpenAIClient"
    - "Agente fiscal_auditor instanciado com deepseek-r1:14b via UnifiedOpenAIClient"
    - "Agente researcher instanciado com llama3.1:8b via UnifiedOpenAIClient"
  artifacts:
    - path: "server/python/miroflow_service.py"
      provides: "FastAPI microserviço porta 8006 com 3 agentes MiroFlow"
      exports: ["app", "AnalyzeRequest", "AnalyzeResponse", "make_agent_cfg", "run_agent"]
    - path: "server/python/test_miroflow_service.py"
      provides: "Testes pytest para REQ-3.1 a REQ-3.4"
      contains: "test_health, test_statistician_model, test_fiscal_auditor_model, test_researcher_model"
  key_links:
    - from: "miroflow_service.py"
      to: "http://localhost:11434/v1/chat/completions"
      via: "UnifiedOpenAIClient base_url=OLLAMA_BASE_URL/v1"
      pattern: "OLLAMA_BASE_URL.*v1"
    - from: "miroflow_service.py"
      to: "server/modules/miroflow"
      via: "sys.path.insert(0, /app/server/modules/miroflow)"
      pattern: "sys.path.insert"
---

<objective>
Preparar o ambiente e criar o microserviço Python MiroFlow (FastAPI, porta 8006) com os 3 agentes especializados configurados para Ollama local.

Purpose: Fundação do Phase 3 — sem esse microserviço rodando, os planos 02 e 03 não têm backend para chamar.
Output: miroflow_service.py funcional + testes pytest cobrindo REQ-3.1 a REQ-3.4 + llama3.1:8b instalado.
</objective>

<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/03-miroflow-embutido/03-RESEARCH.md

<interfaces>
<!-- Padrão de microserviço FastAPI existente — copiar estrutura de bi_analysis_service.py -->
<!-- server/python/bi_analysis_service.py -->
app = FastAPI(title="...", version="1.0.0")
app.add_middleware(CORSMiddleware, allow_origins=["*"], ...)

@app.get("/health")
async def health_check(): return {"status": "ok", "service": "..."}

@app.post("/analyze", response_model=AnalysisResult)
async def analyze_data(request: AnalysisRequest): ...

if __name__ == "__main__":
    port = int(os.environ.get("SERVICE_PORT", 8003))
    uvicorn.run(app, host="0.0.0.0", port=port)

<!-- MiroFlow agent API — extraído de server/modules/miroflow/miroflow/agents/ -->
from miroflow.agents.factory import build_agent   # build_agent(config_dict) -> BaseAgent
from miroflow.agents.context import AgentContext  # AgentContext(task_description="...")
# agent.run(ctx) -> dict com chave "summary" ou texto direto

<!-- Config obrigatória do LLM (campos do base.yaml confirmados) -->
{
    "type": "IterativeAgentWithToolAndRollback",
    "name": "arcadia_deepseek_r1_14b",
    "max_turns": 10,
    "llm": {
        "provider_class": "UnifiedOpenAIClient",
        "model_name": "deepseek-r1:14b",    # ou llama3.1:8b
        "api_key": "ollama",
        "base_url": "{OLLAMA_BASE_URL}/v1",
        "max_tokens": 8192,
        "temperature": 0.7,
        "top_p": 1.0, "min_p": 0.0, "top_k": -1,
        "reasoning_effort": None, "repetition_penalty": 1.0,
        "max_context_length": -1, "async_client": True,
        "disable_cache_control": True, "keep_tool_result": -1,
        "use_tool_calls": False, "oai_tool_thinking": False,
    }
}
</interfaces>
</context>

<tasks>

<task type="auto">
  <name>Task 1: Setup — baixar llama3.1:8b e instalar dependências MiroFlow</name>
  <files>server/modules/miroflow/ (sem modificar), .env (adicionar MIROFLOW_PORT)</files>

  <read_first>
    - /opt/arcadia_merged/.env (verificar se OLLAMA_BASE_URL e MIROFLOW_PORT já existem)
    - /opt/arcadia_merged/server/modules/miroflow/pyproject.toml (confirmar deps necessárias)
  </read_first>

  <action>
    1. Baixar o modelo llama3.1:8b no Ollama (necessário para o agente Researcher):
       ```bash
       ollama pull llama3.1:8b
       ```
       Se o download demorar muito, continuar e validar após. O fallback é llama3.2:3b já instalado.

    2. Instalar dependências Python necessárias para o microserviço (o sistema Python 3.12 já tem fastapi/uvicorn, faltam as deps do MiroFlow):
       ```bash
       cd /opt/arcadia_merged/server/modules/miroflow && uv sync --frozen 2>/dev/null || pip3 install omegaconf hydra-core openai python-dotenv pydantic
       ```

    3. Verificar se MIROFLOW_PORT=8006 existe no .env. Se não existir, adicionar a linha `MIROFLOW_PORT=8006` ao arquivo .env. NÃO alterar OLLAMA_BASE_URL existente.

    4. Verificar se OLLAMA_BASE_URL está presente no .env. Se não, adicionar `OLLAMA_BASE_URL=http://localhost:11434`.
  </action>

  <verify>
    <automated>ollama list | grep "llama3.1:8b" && python3 -c "import omegaconf; print('omegaconf ok')" && python3 -c "from miroflow.agents.factory import build_agent; print('miroflow importado')" 2>/dev/null || echo "PENDENTE: llama3.1:8b pode estar baixando"</automated>
  </verify>

  <acceptance_criteria>
    - `ollama list` contém "llama3.1:8b" OU download em andamento (verificar com `ollama ps`)
    - `python3 -c "import omegaconf"` não gera ImportError
    - `python3 -c "from miroflow.agents.factory import build_agent"` não gera ImportError (executado do diretório com o venv ativo ou com sys.path configurado)
    - .env contém linha MIROFLOW_PORT=8006
  </acceptance_criteria>

  <done>Modelo llama3.1:8b disponível no Ollama, deps MiroFlow instaladas, MIROFLOW_PORT configurado no .env</done>
</task>

<task type="auto" tdd="true">
  <name>Task 2: Criar test_miroflow_service.py + miroflow_service.py (FastAPI porta 8006)</name>
  <files>server/python/test_miroflow_service.py, server/python/miroflow_service.py</files>

  <read_first>
    - /opt/arcadia_merged/server/python/bi_analysis_service.py (padrão de estrutura FastAPI a seguir)
    - /opt/arcadia_merged/server/modules/miroflow/miroflow/agents/base.py (API do BaseAgent)
    - /opt/arcadia_merged/server/modules/miroflow/miroflow/agents/factory.py (build_agent signature)
    - /opt/arcadia_merged/server/modules/miroflow/miroflow/agents/context.py (AgentContext fields)
    - /opt/arcadia_merged/.env (OLLAMA_BASE_URL atual)
  </read_first>

  <behavior>
    - test_health: GET /health retorna {"status": "ok", "service": "miroflow"}
    - test_statistician_config: make_agent_cfg("statistician") retorna dict com model_name="deepseek-r1:14b" e base_url terminando em "/v1"
    - test_fiscal_auditor_config: make_agent_cfg("fiscal_auditor") retorna dict com model_name="deepseek-r1:14b"
    - test_researcher_config: make_agent_cfg("researcher") retorna dict com model_name="llama3.1:8b" (fallback "llama3.2:3b" se llama3.1:8b ausente)
    - test_analyze_request_validation: POST /analyze com agent="invalid" retorna 422 Unprocessable Entity
    - test_analyze_request_schema: AnalyzeRequest aceita {agent, task, context, tenant_id} e AnalyzeResponse tem {agent, model, result, execution_id, duration_ms}
  </behavior>

  <action>
    PASSO 1 (RED): Criar server/python/test_miroflow_service.py com os 6 testes listados em <behavior>. Rodar pytest — DEVE falhar (miroflow_service.py não existe).

    PASSO 2 (GREEN): Criar server/python/miroflow_service.py seguindo o padrão de bi_analysis_service.py:

    ```python
    """
    Arcádia MiroFlow Service — Agentes científicos via MiroFlow + Ollama local
    FastAPI microserviço porta 8006
    """
    import os, sys, time, uuid
    from typing import Optional
    from fastapi import FastAPI, HTTPException
    from fastapi.middleware.cors import CORSMiddleware
    from pydantic import BaseModel

    # Adicionar o submodule MiroFlow ao path
    MIROFLOW_MODULE_PATH = os.path.join(
        os.path.dirname(__file__), "..", "modules", "miroflow"
    )
    sys.path.insert(0, os.path.abspath(MIROFLOW_MODULE_PATH))

    from miroflow.agents.factory import build_agent
    from miroflow.agents.context import AgentContext

    OLLAMA_BASE_URL = os.getenv("OLLAMA_BASE_URL", "http://localhost:11434")

    # Verificar se llama3.1:8b está disponível; senão usar fallback
    RESEARCHER_MODEL = os.getenv("MIROFLOW_RESEARCHER_MODEL", "llama3.1:8b")

    AGENT_MODELS = {
        "statistician": "deepseek-r1:14b",
        "fiscal_auditor": "deepseek-r1:14b",
        "researcher": RESEARCHER_MODEL,
    }

    def make_agent_cfg(agent_type: str) -> dict:
        model = AGENT_MODELS.get(agent_type, "deepseek-r1:14b")
        return {
            "type": "IterativeAgentWithToolAndRollback",
            "name": f"arcadia_{agent_type}",
            "max_turns": 10,
            "llm": {
                "provider_class": "UnifiedOpenAIClient",
                "model_name": model,
                "api_key": "ollama",
                "base_url": f"{OLLAMA_BASE_URL}/v1",
                "max_tokens": 8192,
                "temperature": 0.7,
                "top_p": 1.0, "min_p": 0.0, "top_k": -1,
                "reasoning_effort": None, "repetition_penalty": 1.0,
                "max_context_length": -1, "async_client": True,
                "disable_cache_control": True, "keep_tool_result": -1,
                "use_tool_calls": False, "oai_tool_thinking": False,
            },
        }

    async def run_agent(agent_type: str, task: str) -> tuple[str, str]:
        """Retorna (result_text, model_name)"""
        if agent_type not in AGENT_MODELS:
            raise ValueError(f"Agente desconhecido: {agent_type}. Use: {list(AGENT_MODELS.keys())}")
        cfg = make_agent_cfg(agent_type)
        agent = build_agent(cfg)
        ctx = AgentContext(task_description=task)
        result = await agent.run(ctx)
        text = result.get("summary", str(result)) if isinstance(result, dict) else str(result)
        return text, cfg["llm"]["model_name"]

    app = FastAPI(title="Arcádia MiroFlow Service", version="1.0.0")
    app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_credentials=True,
                       allow_methods=["*"], allow_headers=["*"])

    class AnalyzeRequest(BaseModel):
        agent: str        # "statistician" | "fiscal_auditor" | "researcher"
        task: str
        context: dict = {}
        tenant_id: Optional[int] = None

    class AnalyzeResponse(BaseModel):
        agent: str
        model: str
        result: str
        execution_id: str
        duration_ms: int

    @app.get("/health")
    async def health_check():
        return {"status": "ok", "service": "miroflow", "agents": list(AGENT_MODELS.keys())}

    @app.post("/analyze", response_model=AnalyzeResponse)
    async def analyze(req: AnalyzeRequest):
        if req.agent not in AGENT_MODELS:
            raise HTTPException(status_code=422, detail=f"Agente inválido: {req.agent}")
        start = time.time()
        try:
            result_text, model_name = await run_agent(req.agent, req.task)
        except Exception as e:
            raise HTTPException(status_code=500, detail=str(e))
        return AnalyzeResponse(
            agent=req.agent,
            model=model_name,
            result=result_text,
            execution_id=str(uuid.uuid4()),
            duration_ms=int((time.time() - start) * 1000),
        )

    if __name__ == "__main__":
        import uvicorn
        port = int(os.environ.get("MIROFLOW_PORT", 8006))
        uvicorn.run(app, host="0.0.0.0", port=port)
    ```

    PASSO 3 (REFACTOR): Rodar pytest novamente — TODOS devem passar. Não alterar lógica, apenas organizar se necessário.

    IMPORTANTE: Não usar modelos acima de 14B. Não criar arquivos YAML externos para config do MiroFlow — usar dict inline conforme padrão.
  </action>

  <verify>
    <automated>cd /opt/arcadia_merged && python3 -m pytest server/python/test_miroflow_service.py -x -v 2>&1 | tail -20</automated>
  </verify>

  <acceptance_criteria>
    - `pytest server/python/test_miroflow_service.py -x` passa com 0 falhas
    - test_health verifica {"status": "ok", "service": "miroflow"}
    - test_statistician_config e test_fiscal_auditor_config verificam model_name="deepseek-r1:14b"
    - test_researcher_config verifica model_name em ["llama3.1:8b", "llama3.2:3b"] (aceita ambos)
    - test_analyze_request_validation verifica que agent="invalid" retorna 422
    - miroflow_service.py tem menos de 150 linhas
    - Nenhuma referência a modelos acima de 14B no código
  </acceptance_criteria>

  <done>miroflow_service.py criado e testado. Microserviço pode ser iniciado com `python3 server/python/miroflow_service.py` na porta 8006. Todos os testes passam.</done>
</task>

</tasks>

<verification>
```bash
# Verificar que o serviço sobe:
cd /opt/arcadia_merged && MIROFLOW_PORT=8006 python3 server/python/miroflow_service.py &
sleep 3
curl -s http://localhost:8006/health | python3 -m json.tool
# Esperado: {"status": "ok", "service": "miroflow", "agents": ["statistician", "fiscal_auditor", "researcher"]}
kill %1
```
</verification>

<success_criteria>
- `pytest server/python/test_miroflow_service.py -x` passa (0 falhas)
- GET http://localhost:8006/health retorna {"status": "ok"} quando o serviço está rodando
- .env contém MIROFLOW_PORT=8006
- llama3.1:8b disponível no Ollama (ou download iniciado)
- Nenhum modelo acima de 14B referenciado
</success_criteria>

<output>
Após conclusão, criar `.planning/phases/03-miroflow-embutido/03-01-SUMMARY.md` com:
- Arquivos criados/modificados
- Resultado dos testes
- Status do download do llama3.1:8b
- Modelo de fallback usado para Researcher (se aplicável)
</output>
