Produce and persist the next concrete, scoped research tasks. PlannerAgent creates task dicts from mission + memory/graph + open hypotheses; TaskQueue stores them in SQLite, scores priority, enforces phase/status lifecycle, deduplicates, and hands the next pending task to the loop under hypothesis-terminal and duplicate-fingerprint constraints.
Hold static planning phases (_PLANNING_PHASES, 13 entries from scope_confirmation→report_generation, planner.py:39) and service→attack module map (_SERVICE_ATTACK_MAP, planner.py:57) covering ssh/smb/http/ftp/rdp/redis/… with module/tools/risk/priority.
Offer plan_retry_with_modifications(failed_task, error, attempt, hypothesis_state) (planner.py:123): returns None for permanent errors or terminal hypotheses; otherwise rotates allowed_tools (→ alternative-tool:), doubles timeout_seconds on timeout, lowers priority by 5, tags investigation_method, and ensures fingerprint changes.
Offer plan(mission, target_summary, graph_summary, open_hypotheses?, hypothesis_states?, existing_task_count, phase_filter, max_tasks) (planner.py:191): emits up to max_tasks (default 5) tasks across 5 buckets (scope confirmation when existing_count==0 → asset discovery → service identification → web/api mapping → per-hypothesis independent tool checks that avoid prior fingerprints) with filtering on objective/hypothesis presence, terminal-status exclusion, and seen (key,fingerprint) guard.
Rank hypotheses (planner.py:388rank_unresolved_hypotheses) by 45 + 20*uncertainty + 20*information_value + 10*confidence − 8*attempts − risk_penalty − 10*cost (risk_penalty: low 0, medium 10, high 25).
Define VALID_TASK_PHASES (recon/analysis/test/validate/exploit/post_exploit/report, task_queue.py:21) and _TASK_PHASE_ALIASES for legacy strings (task_queue.py:31).
Persist tasks via create_task(task_data) (task_queue.py:55): calls HypothesisRepository.prepare_task for identity → validates phase/risk/status → computes _score_priority if priority is 0 → inserts tasks row with hypothesis_id/check_fingerprint.
Serve get_next_task(target="") (task_queue.py:119): LEFT JOIN hypotheses WHERE tasks.status='pending' AND (h.id IS NULL OR h.status IN ('open','inconclusive')) ORDER BY priority DESC, created_at ASC — thereby never handing a terminal-hypothesis task.
Support update_task_status/complete_task/block_task (task_queue.py:141), reset_stale_running() (task_queue.py:181 requeues running→pending on resume), get_task, list_open_tasks/list_blocked_tasks/list_completed_tasks (task_queue.py:216), deduplicate (deletes pending dup on target/objective/phase keeping lowest id, task_queue.py:269), reprioritize (rescues all pending via _score_priority), count_by_status/count_total.
agent_loop.run: planner.plan + queue.create_task inside the “no pending tasks” branch; queue.get_next_task at top of every cycle; queue.reprioritize tail of cycle; planner.plan_retry_with_modifications on failed operational errors.
flowchart TD
A[AgentLoop.get_next_task] -->|found| B[scope/risk gate -> executor]
A -->|None| C[planner.plan with memory+graph+hypotheses]
C --> D{filter: has objective&hypothesis?\nterminal? fingerprint seen?}
D -->|pass| E[queue.create_task -> emits task_created]
E --> F{DuplicateInvestigationError / ClosedHypothesisError?}
F -->|yes| G[emit task_rejected -> drop]
F -->|no| H[queue.deduplicate]
H --> I{created ==0?}
I -->|yes && minima met| J[break loop]
I -->|yes && unmet| K[stay alive with reason]
I -->|no| A