Spaces:
Sleeping
Sleeping
Commit History
-
[fix] Give Ollama a real context window (root cause); deterministic weak-check
Hélder Monteiro +1: CodexRoot cause of the 'assessment acts like a chatbot / refuses' behavior: Ollama loaded the model with its default ~4096-token context, which silently TRUNCATED the assessment prompt (system + facts + injected guideline/country tool results) and dropped the instructions — so the model drifted to a greeting. Fix the cause: - agent/loop.py passes options.num_ctx (env NUM_CTX, default 16384) on every chat. - deploy/modal_app.py loads the model with OLLAMA_CONTEXT_LENGTH=NUM_CTX so the server matches the client (no reload on first request). With 16k context lfm2.5:8b now produces a correct, structured assessment on its own (verified over Modal: refugee · sexual-orientation PSG · high · 3 cards). Per feedback, replace the brittle prose phrase-matching weak-detector with a DETERMINISTIC signal: the answer is weak only when the model produced no structured output (case_type/grounds/countries) or almost no text — no guessing at non-deterministic wording. The interview-grounded fallback then r…cd3c555 -
[deploy] Modal-hosted Ollama backend + remote auth headers
Hélder Monteiro +1: CodexAdds deploy/modal_app.py: runs 'ollama serve' (lfm2.5:8b + nomic-embed-text) on a Modal GPU, cached in a Volume, exposed as an HTTPS endpoint protected by Modal proxy auth. Lets a free CPU Space drive the same models over the network. agent/ollama_auth.py supplies auth headers for a remote Ollama endpoint (Modal-Key/Modal-Secret, or Authorization: Bearer) from env; the LLM loop and the guideline-RAG embed call now pass them. No env set = empty headers = local dev unchanged (verified; 77 tests pass). Modal CLI 1.5.0 API verified against the file. Co-authored-by: Codex <noreply@openai.com>accb404
-
[assess] Remove web search entirely (prompt, code, module, test, env)
Hélder Monteiro +1: CodexThe model was narrating web searches and 'current information' it could not actually do, because the assessment PROMPT still told it to use web_search even though the tool was unwired (not in ASSESSMENT_TOOLS). That both misled the user ('feels like web search is enabled') and risked fabricated 'current' claims. - Prompt: drop all web_search references; state plainly there is no web access and to never claim current/latest info it cannot retrieve. - assessment.py: remove the dead web_search status chip and the now-moot outbound-query privacy guard; update the docstring. - Delete the dormant agent/tools/web_search.py module (imported nowhere active). - Tests: drop test_web_search_real and the Tavily gating (assessment uses only local tools now). - .env.example: remove the Tavily key + stale per-phase model vars; note there is no web search. Co-authored-by: Codex <noreply@openai.com>79d3fac -
[fix] Economic recommendations follow the profile, not a hardcoded Gulf list
Hélder Monteiro +1: CodexThe cards contradicted the assessment: the reasoning sensibly suggested Germany/Norway/Canada for a skilled EU resident, but the cards always showed UAE/Qatar/Saudi because economic cases were hardcoded to work_route_countries(). Economic destinations are WORK routes, not asylum, so the model's profile-aware picks are appropriate (and a skilled person needs Western/EU routes, a low-skilled worker needs Gulf/regional). Now use the model's recommended countries (any country), fall back to its looked-up ones, then the curated Gulf shortlist only as a last resort. Cards/roadmap render gracefully for non-Gulf destinations: a 'Regular / skilled-worker visa' label, region, a generic 'check official immigration site' caveat, and the kafala note only where kafala applies. Co-authored-by: Codex <noreply@openai.com>2349f80 -
[docs] Brand the Word (.docx) output to match the PDF template
Hélder Monteiro +1: CodexThe .docx files were bare (plain text wordmark, default fonts, minimal styling) while the PDFs had the full branded template. Bring the .docx to parity: render the Fugee mark to a committed PNG (Pillow) and embed it in a masthead with the Fraunces wordmark + tagline + teal rule, set Inter body / Fraunces teal headings, amber-bold [placeholders], section dividers, a muted subtitle/note, and a page footer — mirroring templates/_head.html. Verified by rendering both to PNG side by side. Note: fonts are referenced by name (Word substitutes if Fraunces/Inter aren't installed); DOCX font *embedding* is intentionally not done. Co-authored-by: Codex <noreply@openai.com>9835d1a -
[ux] Reveal choices in one frame; make assessment always substantive
Hélder Monteiro +1: CodexTwo fixes: 1. Choice controls (esp. the documents CheckboxGroup) rendered empty until an extra Continue. Root cause: the reveal frame sent visible:true ALONE, but a CheckboxGroup whose choices were set while hidden builds no option DOM — the choices must ride along on the reveal. Emit a single frame carrying both visibility and choices (exactly what the working Continue path does); drop the two-pass/sleep entirely. 2. Assessment could come out 'dry' — when the small model returned little parseable text, the fallback was a bare one-liner with no recommendations. Now derive case_type/grounds/risk from the interview as a safety net, write a structured multi-paragraph analysis (classification, ground, risk, why protection must be sought abroad, ranked destinations with real data), and guarantee recommendations via a curated strong-asylum shortlist so the next screen is never empty. The model's own richer narration is still preferred when present. Co-a…ec936b9 -
[assess] Honest handling of economic (non-protection) cases
Hélder Monteiro +1: CodexEconomic migration has no right to asylum under the 1951/1969 Conventions, yet the app showed 'Strong match' asylum cards and a UNHCR/RSD roadmap to everyone regardless of case_type — fabricated legal guidance for an economic migrant (CLAUDE.md: wrong output is worse than no output). - Prompt: explicit rule that economic hardship ('no jobs', 'better life') is NOT a protection ground — classify as economic_or_other and point to regular work/study routes, not asylum. - country_lookup: expose non-signatory economic fields (work visa, job market, kafala, honest strategic_guidance caveats) + a curated work_route_countries() shortlist (Gulf labour markets with employer-sponsored work visas — the historically accessible routes, unlike strict Western/EU criteria). - assessment: for economic_or_other, use the deterministic labour shortlist — never the small model's possibly-Western asylum picks. - recommendations: render economic cases as honest 'Work route' cards (work visa, job…180ae9b -
[docs] Bundle Inter + Fraunces locally — offline PDF generation
Hélder Monteiro +1: CodexDocument PDFs no longer fetch Google Fonts at render time (no internet dependency, and generation dropped ~22s -> ~0.8s): - agent/tools/templates/fonts/: bundled static Latin TTFs (Inter 400/600/700, Fraunces 600/700, ~280KB, committed). - _head.html: local @font-face; non-Latin scripts (Arabic/CJK/Devanagari/ Cyrillic) fall back to the system Noto fonts (also offline). - doc_generator passes base_url=templates dir so the @font-face urls resolve. Verified: no font warnings; English + Arabic documents render offline as valid PDFs. Co-authored-by: Codex <noreply@openai.com>819eaa8 -
[brand+ux] Rebrand to Fugee, render markdown, branded detailed documents
Hélder Monteiro +1: Codex- Rebrand Refuge -> Fugee across all user-facing text (wordmark, site bar, page title, agent persona, document headers). Word-boundary replace so the legal term "Refugee" is untouched. Repo/package name stays "refuge". - app/mdlite.py: tiny Markdown->HTML (bold/italic/code, #headings, -/1. lists, paragraphs). Assessment reasoning + chat bubbles now render markdown instead of showing literal ** and #. - Documents: branded masthead (Fugee mark + wordmark), Fraunces/Inter web fonts, page footer + page numbers, nicer headings/steps/contacts styling — for both the PDF and (wordmark header) the editable Word files. 58 fast tests pass; verified PDF carries the brand + footer and markdown renders. Co-authored-by: Codex <noreply@openai.com>aefd221
-
[agentic] Ground assessment in UNHCR guidelines via a RAG tool
Hélder Monteiro +1: CodexIntegrate specs/data/guidelines (UNHCR Handbook + Guidelines No. 1-14) so the agent cites real guidance instead of inventing law — a local, pure-Python retrieval tool (not an external wiki compiler). - data/scripts/build_guidelines_index.py: extract PDF text, chunk, embed with a local Ollama embedding model (nomic-embed-text), write a JSON index (2023 chunks; gitignored/regenerable). - agent/tools/guideline_search.py: semantic top-k search (cosine over cached vectors) returning real guideline excerpts; AgentTool guideline_search. - assessment: guideline_search registered first in ASSESSMENT_TOOLS; prompt now instructs the agent to look up the relevant ground in the Handbook/Guidelines and cite it. Tool-status chip "Consulting UNHCR guidelines: …". Verified live: the Afghan gay man's case is assessed as refugee / "particular social group", grounded in Guideline No. 9 + the Handbook. Deps: pypdf already present; uses ollama embeddings (retrieval, not generation). Co-authore…f963859 -
[agentic] Port pi loop hooks + LLM-drafted editable (Word) documents
Hélder Monteiro +1: CodexAudited /tmp/pi and ported its agentic control architecture into Refuge's Python loop, then used it to make the assessment and documents genuinely agentic (the hackathon point: small-LLM agentic AI). Agent framework (agent/loop.py) — Python port of pi's AgentLoopConfig hooks: - LoopHooks: transform_context, before_tool_call, after_tool_call, should_stop_after_turn, prepare_next_turn; plus tool `terminate` handling. Assessment now uses the hooks: - should_stop_after_turn ends the run once the @@ASSESSMENT block is produced (no rambling); before_tool_call blocks web_search queries containing the person's private narrative (privacy, ARCHITECTURE §Security). Documents are now agentic + editable: - agent/drafting.py: the LLM drafts an in-depth, first-person personal statement from the person's own answers, in their language, with [placeholders] to complete (deterministic fallback if the model is down; nothing fabricated). - doc_generator: exports every document as editable Word…b93c45d -
[T049-T055 + UX] Phase 5 documents + single-model + responder/UX fixes
Hélder Monteiro +1: CodexPhase 5 — Document package: - agent/tools/doc_generator.py + 4 WeasyPrint templates: real PDFs from session data, amber-highlighted pre-fills, blanks (never [NAME]/PLACEHOLDER), logged field sources; zip for "Download all"; head-less preview snippet - app/phases/documents.py: preview + evidence checklist (pre-checked from documents_available) + file list + Download all + Start over - nav: Recommendations "Prepare my documents" -> Documents; Start over -> intake - launch(allowed_paths=[tempdir]) so Gradio can serve the generated PDFs - tests: 8 document tests (real PDF text via pypdf 6.12.2, pinned one-before) Single LLM + UX fixes (from live testing): - one model end-to-end: MODEL_ID=lfm2.5:8b (fast ~2s, tool-calling + good multilingual); removed per-phase model split (no fallback, hackathon rule) - languages limited to those lfm2.5:8b handles well (probe-verified): English, French, Spanish, Portuguese, Arabic, Hindi, Chinese, Japanese, Korean, Russian - responder: free-text…f05e15f -
[T044-T048 + data] Phase 4 recommendations + real UNHCR stats integration
Hélder Monteiro +1: CodexPhase 4 — Recommendations + roadmap: - app/phases/recommendations.py: 2-3 country cards (flag, Fraunces name, match badge from real fields, real facts block, prep details, select button), auto- select top country, vertical roadmap (>=5 steps, country-adapted, contact/time chips, header names country). Cards/facts come from country_lookup data. - app/app.py: assessment -> recommendations handoff via .then() - tests/integration/test_recommendations.py (offline, real country data) Data layer — downloaded, integrated, tested (curated stays authoritative): - country_lookup: curated specs/data/countries.json is ALWAYS the structural base (orgs, signatories, asylum systems); countries_enriched.json overlays ONLY numeric stats.* (never replaces curated fields). Added resolve_iso3. - fix enrich_downloader: ISO3 is "iso" not "iso3" (was enriching 0/152 -> 151); complementary protection is "dec_other" not "dec_complementary" - fix query_data: filter on coo_iso/coa_iso (was coo_code/c…7a05b1b -
[T035-T043] Phase 3 — situation assessment (reasoning stream + real tools)
Hélder Monteiro +1: Codex- agent/tools/country_lookup.py: offline lookup from countries.json (enriched if present), flat schema (unhcrPresence, processingTimeMonths, acceptanceRate, primaryLanguage, requiredDocuments, legalAidOrgs, unhcrOffice, tier, warning); not_found instead of fabrication; PENDING stats surfaced as None (T036/SC-026) - agent/tools/web_search.py: real Tavily search via httpx; fails loudly w/o key or on API error (no fake results) (T035/SC-025) - app/prompts/assessment.md: 5-step scaffold (1951 + AU 1969 grounds, transit safety, find programmes, rank 2-3) + @@ASSESSMENT structured block (T038) - app/assessment_parse.py: parse grounds/risk/countries from the block - app/phases/assessment.py: facts sidebar (8 fields), live reasoning stream, progress bar, "Searching: …" tool chips; stream_assessment core runs the model with both tools, then attaches authoritative country_lookup data to ranked recommendations (origin excluded) into session.assessment (T037/T039/T040) - app/app.py…4270181 -
[T017-T029] Phase 1 — agent loop + interview core
Hélder Monteiro +1: Codex- agent/loop.py: full turn cycle (already streamed); separate system prompt from persisted history (no accumulation); create_loop() session factory (T019) - app/prompts/interview.md: 4 sub-phase question bank + @@RESPONDER directive (T020) - app/prompt_loader.py: compose system + interview prompts (T021) - app/responder.py: parse @@RESPONDER -> ResponderSpec, lenient fallback - app/phases/interview.py: mockup-faithful progress rail, chat bubbles, thinking indicator, structured responder (choice/country/text), ghost "Save and continue later"; real per-delta streaming; turn_end sets responder mode; agent_end advances state (T022-T025) - app/countries.py: country selector choices from countries.json - app/design_tokens.py: align CSS var names to mockup (--primary/--bg/--r-md/ --s-md) + shadows/maxw from DESIGN §4/§5; app.py hero updated - app/app.py: mount interview, auto-start first question via demo.load Tests (33 pass): responder parse, event contracts (T028), loop isolation…e16f16c -
[T006] Loop: gracefully degrade when model rejects `think` param
Hélder Monteiro +1: Codexqwen2.5:7b (the spec default) 400s on any `think` value. The error is raised when the stream starts iterating, so _iter_chat retries once without `think` (only before any chunk is produced) and caches _supports_thinking=False for the session. Verified against qwen2.5:7b (no-think) and qwen3.5:4b (think) on the remote host — both stream to completion. Co-authored-by: Codex <noreply@openai.com>f868661 -
[T001-T016] Phase 0 — project setup: agent loop, state machine, Gradio skeleton
Hélder Monteiro +1: Codex- requirements.txt + pyproject.toml pinned one release before latest (gradio 6.15.2, weasyprint 68.1, httpx 0.28.0, ollama 0.6.1, litellm 1.87.1, pytest 9.0.2, pytest-asyncio 1.3.0) - agent/events.py: 8 typed AgentEvent dataclasses - agent/loop.py: pure-Python async AgentLoop (stream, tools, steer, abort, error-as-event); agent/tools/base.py AgentTool contract - app/state/session.py: forward-only interview state machine - app/app.py + app/design_tokens.py: Gradio skeleton, DESIGN.md :root tokens - app/prompts/system.md: Refuge case-worker persona - scripts/validate_design_tokens.py: T010 token validator - tests/unit: state machine (7) + loop ping (1) — all green Co-authored-by: Codex <noreply@openai.com>944f1ac