Spaces:
Sleeping
Sleeping
Commit History
-
[assess] Agentic guard against junk/jailbreak input; tidy economic UX
Hélder Monteiro +1: CodexPer feedback that a smart agent should refuse to assess garbage rather than fabricate a result — and that this must be MODEL REASONING, not hardcoded prose matching: - Prompt: a precedence GATE checked before any category. If the answers are nonsensical / test strings / code / contradictory / devoid of real info, the agent sets case_type=unclear, leaves grounds+countries empty, and asks for a genuine account. 'No protection ground' now requires a genuine account — junk is unclear, never economic (the bucket the model was wrongly falling through to). - assessment.py: an unclear case yields NO recommendations (don't invent destinations from junk); _synth_reasoning returns a clean 'I couldn't determine your situation' message instead of a protection narrative. - recommendations.py: a 'needs more info' panel (no cards, no roadmap, proceed hidden) when there are no recs; the economic intro is trimmed to one line since the full honest explanation already lives in the assessm…9c6656b -
[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 -
[fix] Treat chatbot-greeting assessments as weak (use grounded fallback)
Hélder Monteiro +1: Codexlfm2.5:8b sometimes answered the assessment with a conversational greeting that asked the person for info it already had ('Could you tell me your preferred language?… please share … when you're ready'). That isn't a refusal, so it slipped past _is_weak_reasoning and leaked to the screen. Add greeting / request-for-info markers so these are treated as weak and replaced by the deterministic interview-grounded analysis + recommendations. Co-authored-by: Codex <noreply@openai.com>e88a54f -
[fix] Robust assessment + 2-3 recs + documents progress
Hélder Monteiro +1: CodexThree issues found in the live test (small-model quality + UX), not deployment: 1. Assessment could show a refusal ('I cannot determine a case type…'). The rich deterministic fallback only triggered when the narration was <120 chars, so a long non-answer leaked. Add _is_weak_reasoning() (refusal markers + length); when weak, take the interview-derived case_type/grounds/risk and the structured _synth_reasoning instead of the model's non-answer. 2. Recommendations always showed ONE country (we only topped up when the list was empty). Now top up protection cases to 2-3 using the person's own stated destination_preferences (looked up, signatory-only), then strong curated systems; cap at 3. Algeria case now -> Sweden/France/Italy. 3. 'Prepare my documents' looked frozen during the LLM draft. show_documents is now an async generator: reveal the docs screen with a 'Preparing your document package…' status immediately, then render the finished Word+PDF package. C…6614702
-
[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] Hide doubled 'processing' spinner; never leak @@END / metadata
Hélder Monteiro +1: Codex1) Two 'processing…' overlays appeared in the interview responder because the choice controls are now always mounted-visible, so Gradio painted its generic per-component progress spinner on each. Set show_progress='hidden' on the interview events (Begin + Continue + reveal); the conversation itself signals progress and the assessment has its own bar. 2) The assessment leaked '@@END' and a raw 'Summary of my assessment' list when the small model wrote the metadata as prose instead of the @@ASSESSMENT block. parse_assessment now recognises the metadata in three forms (the @@ASSESSMENT block, a 'Summary of my assessment' section, or a bare run of field: lines), strips all of it plus any stray @@ASSESSMENT/@@END markers from the visible reasoning, and parses fields tolerant of bullets and parenthetical glosses. Co-authored-by: Codex <noreply@openai.com>3c64ee8 -
[fix] Choice controls never toggle visibility; non-signatories not asylum dests
Hélder Monteiro +1: Codex1) The recurring 'press Continue to show options' bug. Real root cause: toggling a gr.CheckboxGroup from hidden->visible does NOT build its option DOM, so it paints empty until the next interaction — no in-generator/round-trip trick fixed it. Permanent fix: mount the choice controls (radio + both checkbox groups) VISIBLE and drive them by their CHOICES alone (empty = collapsed via a CSS :has rule). Setting choices on an already-mounted-visible group always renders (the one behaviour we'd confirmed works). country/text still toggle visible (those components handle it fine). This also kills the 'all controls visible / stale English labels' leak. 2) Pakistan (a non-signatory with no UNHCR) was shown as an asylum destination with a UNHCR/RSD roadmap. Protection-case recommendations now require the country to be a Convention signatory; non-signatories are dropped. And the roadmap's first step adapts: 'Register with UNHCR' only where UNHCR runs registrati…d22000a -
[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 -
[ux] Show multi-select options instantly (no extra Continue)
Hélder Monteiro +1: CodexThe single shared CheckboxGroup swapped its choices at reveal time, which Gradio renders a beat late — so the persecution-grounds / documents options only appeared after an extra Continue press. Give each multi-select question its own pre-mounted group whose (language-correct) choices are set once and never swapped; only visibility toggles thereafter, so options paint at once. Co-authored-by: Codex <noreply@openai.com>597cc8f
-
[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 -
[fix] Fully-scripted multilingual interview (no LLM) + remove walkthrough chrome
Hélder Monteiro +1: CodexPer testing feedback: letting the LLM phrase interview questions caused wrong questions, mismatched controls (Yes/No on a free-text question), repeats, and drift. Replaced it with a deterministic, fully-scripted interview. - app/interview_script.py: fixed question bank, each with a fixed control, pre-translated into all 10 languages, templated with collected values (e.g. "what happened in {origin}"). No model is used in the interview at all. - app/phases/interview.py rewritten: pure lookup/render; controls are correct by construction; one question at a time; guaranteed progression to Review; on confirm it advances state -> ASSESSMENT (language-independent trigger). - reason-for-leaving is now a free-text narrative (templated with origin), not reductive pills, per feedback. - removed the meta "Phase N — …" walkthrough descriptions from every screen (they were design-walkthrough scaffolding, not product UI). - app.py: assessment trigger keys off state==ASSESSMENT (was a frag…e6d3ad1 -
[fix] Deterministic slot-filling interview (agentic flow, consistent UI)
Hélder Monteiro +1: CodexThe interview previously depended on the small model to emit a @@RESPONDER directive and drive phase progression — which it did inconsistently (controls flip-flopped, intro varied, questions repeated, phases never advanced so the flow never proceeded to assessment). Re-architected the pi way: the APP deterministically drives the loop, the LLM only phrases. - app/phases/interview.py: a fixed, ordered SLOTS plan; each slot has a fixed control (country picker / choice pills / text) and target session.interview field. The app shows the right control every time, advances one step per turn, drives the phase rail, and always reaches Review -> Assessment. The LLM only phrases each question warmly in the chosen language and acknowledges the prior answer (thinking off). Answers are captured straight into session.interview (real data for the assessment facts + documents). - removed the brittle directive parsing from the interview path. - tests updated to the deterministic contract; r…aa796c4 -
[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 -
[T030-T034] Phase 2 — intake screen + language selection + theming
Hélder Monteiro +1: Codex- app/phases/intake.py: mockup-faithful intake (#phase-1) — topo backdrop, logo+wordmark+tagline, 8 language pills in native script, amber "Begin in <language>" primary, privacy trust chip. Single-select w/ click-to-deselect. - pure logic: select_language, begin_interview (ValueError w/o language), toggle_selection, begin_label — all unit-tested (T034) - intake -> interview navigation: Begin hides intake, reveals + streams the interview in the chosen language (language-aware bootstrap) - app/app.py: global theme/chrome overrides (hide Gradio footer, body bg, fonts, flatten default component boxes, focus rings); mount intake + hidden interview; removed standalone hero (intake owns the wordmark now) - interview.build(visible=...) so it can start hidden - scripts/validate_design_tokens.py: also scan intake/interview CSS modules Tests: 40 pass (incl real-model integration). Token validator PASS. Co-authored-by: Codex <noreply@openai.com>fa21080 -
[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 -
[T011+] Env config: uv venv, .env loader, remote Ollama host + model
Hélder Monteiro +1: Codex- app/config.py: dependency-free .env loader (OLLAMA_HOST/MODEL_ID/MODEL_PROVIDER) - .env.example committed; .env gitignored (points at remote 192.168.0.233:11434, MODEL_ID=qwen3.5:4b — qwen2.5:7b absent on host) - app.py loads .env at startup; tests/conftest.py loads it for test runs - venv recreated with uv Co-authored-by: Codex <noreply@openai.com>2af1cec -
[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