I gave my STRIDE threat modelling tool an agentic interview mode A developer added an agentic interview mode to their STRIDE threat modeling tool, P2 Threat Model Generator, which reads Docker Compose, OpenAPI, and Kubernetes manifests. The new mode uses an LLM to interview users turn-by-turn, extracting application details into a structured descriptor while keeping all security reasoning deterministic in code. The tool is built on an in-house agent harness called agent-core, with a tool surface of twelve functions, and supports multiple LLM providers including Claude and Ollama. Back in June I shipped P2 Threat Model Generator — a Python tool that reads Docker Compose, OpenAPI, and Kubernetes manifests, runs STRIDE analysis, scores threats, and spits out HTML + JSON reports with MITRE ATT&CK mappings. It works. It's boring. That's fine — the boring parts rule engine, scorer, reporters are the parts you actually want deterministic. The part that isn't boring is getting the input in the first place. Real threat modelling conversations don't start with a YAML file. They start with: "So what does this service actually do?" So I added an agentic mode . I wanted to keep every single deterministic piece — the STRIDEScorer , the compliance flag matcher, the HTML reporter — untouched. All the security reasoning stays in code. The LLM only does one job: talk to the user and figure out what to feed into ApplicationDescriptor. That means the agent isn't "generating threats" or "reasoning about security." It's an interviewer with a schema . Both my SAST/DAST triage tool P1 and this one now run on the same in-house harness — agent-core . Small library, three primitives: python from agent core import Agent, tool, ToolRegistry from agent core.models import ToolRisk, ExecutionPolicy @tool def example : ... ToolRegistry ... Agent ... @tool https://dev.to/tool decorates a function. ToolRegistry bundles them. Agent runs the model and handles tool calls. Policies control which risk tiers are permitted NONE, FILESYSTEM, EXECUTION . Providers are swappable — Claude for interactive work, Ollama for offline runs. The tool surface Twelve @tool https://dev.to/tool functions do everything the agent can do. The star of the show is ask user: @tool https://dev.to/tool description= "Ask the human user a single question and return " "the answer as a string. Ask ONE question at a time; " "wait for the answer; then decide the next question." , risk=ToolRisk.NONE, def ask user question: str - str: print f"\n🤖 {question}" answer = input "👤 " .strip return ok answer=answer The rest split into three groups: Build the descriptor — start app, set compliance, add component, add data flow Inspect state — list components, list flows, get app Run the pipeline — run stride analysis, enrich threats, score threats, generate report Every tool takes a session id and threads state through a module-level store: class SessionState TypedDict : descriptor: ApplicationDescriptor | None threats: list Threat model: ThreatModel | None STORE: dict str, SessionState = {} Not thread-safe. Doesn't need to be. This is a single-process CLI. The system prompt You are a senior threat-modelling analyst using STRIDE. Your job is to interview a human user about their application, extract components and data flows, and produce a threat model report. Workflow: Rules: The important line is: "Do not invent user input — always ask." Without it the model happily hallucinates an entire architecture and skips straight to the report. ask user returning stdin is what keeps it honest. Why this pattern beats "extract from prose" The obvious alternative: user writes a paragraph describing their app, agent parses it, calls the builder tools, done. I tried that first. Two problems: If the paragraph doesn't mention rate limiting, the agent picks a plausible default and moves on. You get a threat model with has rate limiting=True for a component that has no such thing. The user gave you their description; you can't ask them: "Wait, does this API actually authenticate?" without breaking the single-shot contract. Turn-by-turn interviewing via ask user fixes both. The model can ask when it needs information, and it can ask follow-ups whenever it hits a gap. Trade-off: latency. A full interview is 20–40 tool calls. With Claude, it takes around 2 minutes. Ollama with a 70B local model takes 8–10 minutes. Testing Twenty-six tests, all mocking the provider so nothing hits a real LLM in CI: def test ask user returns input - None: with patch "builtins.input", return value="my-app" : result = ask user question="What is the app name?" data = json.loads result assert data "answer" == "my-app" The smoke test verifies the whole pipeline wires up without touching Anthropic or Ollama: def test full pipeline smoke tmp path - None: provider = MagicMock fake result = AgentResult output="Threat model generated: 12 threats, 2 critical", stop reason=StopReason.DONE, iterations=15, tool calls= , agent = ThreatModelAgent provider=provider, max iterations=5, agent. build agent = MagicMock agent. build agent.return value.run.return value = fake result result = agent.run session id="smoke1", describe="e-commerce API with Postgres and Redis", output dir=str tmp path , assert result.stop reason == StopReason.DONE Live tests are manual and off the critical path. CLI Interactive Claude interview python p2 threat model.py --agentic --provider claude Seeded with a paragraph so the agent has context before asking questions python p2 threat model.py --agentic --provider claude \ --describe "FastAPI backend, Postgres, public login, stores customer data" Local / free / slow python p2 threat model.py --agentic --provider ollama Reports land in: ./output/-threat-model.{json,html} What I'd change Persist session state between runs. Right now the interview is single-shot. Interrupting halfway loses the descriptor. A pickle or JSON dump per session would fix that. Tool-call replay for debugging. The agent-core tracer captures every call but there's no CLI to replay a session offline. A dry-run mode. Print the questions the agent would ask so you can eyeball the tool-call graph. Repo github.com/PyHackSecGP/p2-threat-model-generator branch feat/... Same treatment coming for P3 log anomaly detector and P4. The whole point of building agent-core was that once you have the harness, adding an agentic mode to any deterministic tool is a week's work. If you're building agentic security tools and want to trade notes, I'm at greenbladesec.com.