Skip to main content

"Pick something. Get good at it. See if you can be the best at it." — Jordan Peterson

FPF Agentic Thinking Map

v1.6.0 — a compact runtime map for LLM agents.

Built from FPF (First Principles Framework) as a bounded traversal core: explicit state, lawful next move, inspectable outcomes.

Python 3.12+ · MIT · zero runtime dependencies

Published as a small community implementation: free to use, open to inspect, and meant to be a practical point of discussion rather than a total framework.

At a glance

PyPI version Python versions License Zero dependencies Verify

Live demo Per decision Traversal Runtime shape

Badges above describe the core engine (fpf_thinking_map/, what PyPI ships). dev_mcp/ is separate dev-only tooling for agentic testing against that engine — not shipped, own test suite, own count, on purpose kept distinct rather than folded into the numbers above:

dev_mcp self-test dev_mcp compliance mode dev_mcp advisories


What this is

This map keeps agentic traversal clean.

It does not run heavy semantic payload inside the step loop.
It does not force reasoning-on-reasoning recursion.
It does not let traversal bloat with generated thinking trash.

The model stays free to generate.
The map only keeps runtime state and checks the next lawful move.


Why this exists

In long multi-step runs, models waste budget on self-management:

  • re-checking what was already checked
  • re-deriving state from prior prose
  • re-arguing about their own prior reasoning

That is where drift and context noise come from.

This package moves traversal bookkeeping to code:

  • context
  • roles
  • transitions
  • evidence freshness
  • guards and blockers
  • outcome kind

So the model spends capacity on the task, not on traversal clutter.


Why this isn't another prompt

Most agent setups today handle this with prose: a CLAUDE.md, a system prompt, a rules file telling the model to behave. That's still just tokens sitting in context, waiting to be silently deprioritized or reinterpreted as the conversation grows.

This package doesn't ask the model to remember discipline, it removes the need for discipline. Legality of the next move is computed in code (GatePrimitive, TransitionPrimitive, required evidence) — outside the token stream, so it can't be silently reinterpreted the way prose instructions can be. That's a structural difference, not a wording difference.


Ignition Lock — human-in-the-loop for destructive moves

"HITL" is the generic name for the category. This is the specific mechanism: slice() and attempt_transition() already talk about whether a move can firecan_fire, fires normally. Ignition Lock is what sits on top of that vocabulary, not bolted onto it: a transition can be fully legal, evidence fresh, gate green, cleared for launch in every sense the FPF logic computes — and still not cleared to fire without a human turning the key.

Full-autonomy agentic runs are normal now — an agent frames a problem, collects evidence, and drives itself state to state without a human reading every step. Most of that traversal, this map is happy to let through: gates pass, evidence is fresh, fire.

Destructive and irreversible moves are the exception, and the FPF logic underneath doesn't get more cautious about them on its own. If delete_records has its required evidence and its gate is satisfied, the traversal is legal and says CONTINUE — same as any other move. That's correct behavior: the map has no built-in notion that deleting something is different from deploying something. It shouldn't have to — that distinction belongs to a separate layer, not baked into every gate.

requires_human_authorization is that layer — the field name says exactly what it checks, on purpose: there's no bundled system prompt telling the model what this flag means, so the name has to carry that on its own when the model reads it cold in a step()/slice() response. Mark a transition requires_human_authorization=True and the engine keeps computing and reporting its legality — evidence and gate status are still shown in full — it just refuses to fire without authorized=True, enforced at ActiveState.transition_to() itself, so there's no lower-level call that skips it. The model can see the delete is ready. It cannot pull the trigger.

Where that "yes" comes from matters. authorized is a plain argument — nothing inside this engine stops a caller with direct access to it from setting authorized=True on its own. This library has no identity system and isn't getting one; that boundary is the integration's job, not the engine's. Wiring it correctly means whatever harness sits around this engine — an MCP server, a CLI, a chat approval step — never exposes authorized as something the model's own tool-calling loop can set for itself. It has to come from a channel the agent can request but not answer on its own behalf: a human typing a confirmation, a separate approval endpoint, an explicit "yes / go".

The waiting itself is a fact worth keeping, not just the refusal. current_state="ready_to_restart" looks identical whether a human is mid-decision on delete_records or nobody's touched it yet — that's the gap ADV-08 already flags for this engine generally, sharpened here. ActiveState.pending_authorizations (a set[str], plural on purpose) names every transition a human is currently being asked about the moment requires_human_authorization escalates, and each one survives past that one call: it's a plain constructor field, not one of the private counters ADV-08 warns about, so a harness restoring state after a restart can pass it straight back in. Plural mattered in practice, not just in theory — an earlier single-value version of this field silently lost track of a still-pending ask the moment a second, different transition also escalated before the first was resolved, found by testing two concurrent destructive requests against the live engine. Each transition_id is removed only when that same one fires authorized — firing something unrelated does not touch it — and step() surfaces a warning for every entry still pending, regardless of which move is in view, so none of them quietly fall out of context. If an ask goes stale — the model moved on, the question no longer applies — call resolve_pending_authorization(transition_id). Nothing here assumes "pending" always resolves to "yes".

See run_scenario_destructive_hitl for the full walk: evidence present, gate passing, still refused until authorized.

Abort to Orbit — when a human says no

A denial is a fact, not a dead end. Escalating for every destructive move regardless of whether a legitimate non-destructive path existed too would be its own failure — the exact shape of denying a database wipe for reasons nobody could see, because nothing about the alternative was ever visible.

NASA's Shuttle program had a real abort mode with this name: abort the risky trajectory, still reach a stable, useful orbit instead of a hard failure. That's the shape of a denied delete_records rerouting to a fired archive_records — destructive aborted, task still lands somewhere useful.

TransitionPrimitive.safe_alternatives names a transition's non-destructive twins — explicit, declared, the same way incompatible_with and bridges_to already work in this codebase. Never inferred: two transitions merely sharing a from_state are not assumed to be substitutes for each other. slice() surfaces them before the model ever attempts the destructive move, and the ESCALATE Outcome carries them again if it does — so the option is visible at the point of decision, not just discovered after a refusal.

ActiveState.deny_pending_authorization(transition_id, reason) records an explicit "no" — distinct from the stale-ask case above. It doesn't permanently lock the door (a human can change their mind; a later authorized=True still fires), but any retry's ESCALATE reason names what was said before, so it's never silently re-asked as if nothing happened. It also doesn't pick an alternative for you — the engine names the safe twin, the model chooses to fire it, through the same ordinary attempt_transition() as any other move. Whether the archive is actually an adequate substitute for the delete is a domain judgment this library can't make; making sure that judgment has something visible to work with is what it's for.

See run_scenario_denied_reroute: the same escalation, this time denied with a reason, then resolved by firing the declared alternative directly — destructive denied, task still done.

None of this helps if the gate was never set. requires_human_authorization defaults to False — unguarded — and nothing in the engine checks whether a transition's own name suggests it should have been True. A map author (or an LLM co-building the map) writing delete_everything without the flag is a silent gap this library can't catch by guessing intent from a string. What it can do: ADV-10 is a dev_mcp lint — keyword-heuristic, not semantic, not enforcement — that flags exactly this shape when it's present in a scenario's map, so the omission has to be noticed instead of just hoped past.


Runtime contract

Each step returns a compact JSON slice:

  • where the agent is
  • what can fire
  • what is blocked
  • what evidence is missing or stale
  • what outcome applies

Outcomes include:

  • CONTINUE
  • COLLECT_EVIDENCE
  • BRIDGE
  • IDLE
  • ESCALATE

The map constrains traversal legality.
It does not overwrite user meaning and does not replace model intelligence.


Measured per step

This was tested on 5 shipped decision points.

  • compiled state.slice() averaged 481.4 tokens per decision
  • raw FPF exact-section prompt averaged 138977.2 tokens per decision
  • that is 288.7x smaller per decision
  • in live billed input tokens, compiled averaged 537.4 vs raw 139194.6
  • that is a 259.0x live per-decision input gap

Full measurement: TRIPLE_TAX_CALCULUS.md


Scope

This package is intentionally narrow.

It is for:

  • bounded, stepwise agent traversal
  • clearer failure signals
  • lower runtime noise
  • inspectable behavior
  • Ignition Lock — HITL gating on destructive/irreversible transitions (requires_human_authorization), with declared non-destructive alternatives (safe_alternatives) so a denial routes somewhere instead of dead-ending

It is not:

  • full semantic ingestion of FPF
  • a universal reasoning engine
  • a replacement for application logic
  • an in-engine memory/retrieval system (no embeddings/vector store inside this engine)

Quick start

# Python 3.12+
python -m fpf_thinking_map.verify
python -m fpf_thinking_map.examples

Install:

pip install fpf-thinking-map

Minimal usage

from fpf_thinking_map import (
    SemanticMap,
    ContextPrimitive,
    RolePrimitive,
    TransitionPrimitive,
    GatePrimitive,
    GateCheck,
    RuntimeBinding,
    ThinkingMapTraversal,
)

sm = SemanticMap()
sm.register_context(ContextPrimitive("deploy", "Deploy Context"))
sm.register_role(RolePrimitive("owner", "Owner", "deploy"))
sm.register_gate(
    GatePrimitive(
        "release_gate",
        "Release Gate",
        "deploy",
        checks=[GateCheck("tests", "Green tests", required_evidence=["test_results"])],
    )
)
sm.register_transition(
    TransitionPrimitive(
        "ship",
        "Ship release",
        "deploy",
        "candidate",
        "released",
        required_evidence=["test_results"],
    )
)

engine = ThinkingMapTraversal(sm)
state = engine.build_active_state(
    RuntimeBinding(
        task="release",
        actor_role_ids=["owner"],
        active_context_id="deploy",
        current_evidence=["test_results"],
    ),
    current_state="candidate",
)
outcome = engine.step(state)
print(outcome.kind)  # CONTINUE / COLLECT_EVIDENCE / BRIDGE / IDLE / ESCALATE

The engine is domain-agnostic. You define your own contexts, evidence, gates, and transitions.


Relationship to FPF

Based on ailev/FPF by Anatoly Levenchuk.
Acknowledged as inspiration and source material, not as a scope lock.

This package is an independent implementation, MIT-licensed, and open to reuse in other developments. It keeps its own runtime scope and, where needed to preserve that scope, omits or explicitly rejects parts of FPF rather than inheriting the framework as an inseparable whole.

FPF is the broad frame.
This package is the compact runtime traversal tool.


Community and attribution

  • Maintained by: igareosh.com
  • Contact: igareosh@igareosh.com
  • GitHub / Telegram: @igareosh
  • Inspiration acknowledged: Anatoly Levenchuk / ailev/FPF

Plain-language attribution and scope boundaries live in NOTICE.


Provenance

Repository-wide SHA-256 fingerprints live in SHA256SUMS. They give a simple integrity proof for the tracked source state that ships with this repository.


Design principles

  • add structure only when behavior improves
  • keep per-step payload small
  • keep legality checks explicit
  • keep model generation free
  • optimize for inspectability

Compatibility

Works with model families that can read structured JSON and follow constraints. No model-specific prompt protocol is required by the engine itself.


Deep technical notes (optional)

If you need theory, adoption/rejection rationale, and analysis provenance, use:

Testing this package's behavior against the documented integrator advisories (evidence staleness, risk-level filtering, bridge trust, and the rest)? dev_mcp checks scenario runs against all 8 automatically and keeps a log of what fired — useful if you're integrating this into your own agent and want to know which sharp edges your scenarios actually touched, not just which ones exist on paper.

Mainstream docs stay focused on runtime behavior and integration.


License

MIT. See LICENSE.

For ownership, attribution, and scope notes, see NOTICE.


"All speech is vain and empty unless it be accompanied by action." — Demosthenes

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

fpf_thinking_map-1.6.0.tar.gz (56.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

fpf_thinking_map-1.6.0-py3-none-any.whl (55.4 kB view details)

Uploaded Python 3

File details

Details for the file fpf_thinking_map-1.6.0.tar.gz.

File metadata

  • Download URL: fpf_thinking_map-1.6.0.tar.gz
  • Upload date:
  • Size: 56.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for fpf_thinking_map-1.6.0.tar.gz
Algorithm Hash digest
SHA256 3cbce462ae3f95d9b50e1dad4bd8e85d137673a1fdbfd35bc5b04652bd1a8141
MD5 a0bb59ad393252f2938e9619877d4051
BLAKE2b-256 8a22f1763dda871843b3900d207c693c6190c787df011c85e76ddb1cc5dab198

See more details on using hashes here.

Provenance

The following attestation bundles were made for fpf_thinking_map-1.6.0.tar.gz:

Publisher: publish.yml on igareosh/fpf-agentic-thinking-map

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file fpf_thinking_map-1.6.0-py3-none-any.whl.

File metadata

File hashes

Hashes for fpf_thinking_map-1.6.0-py3-none-any.whl
Algorithm Hash digest
SHA256 91fe9d596dd11382b7f7b37a5115ee4f0df1415eac7d7cd7fcbf8b6b01f5831f
MD5 fb6dcce2145b9ba1311d233ef3932bab
BLAKE2b-256 003be3b839e28d1ff644f63c2de6aba2c8eecd8bb7f4bc13b4abb39c3f128420

See more details on using hashes here.

Provenance

The following attestation bundles were made for fpf_thinking_map-1.6.0-py3-none-any.whl:

Publisher: publish.yml on igareosh/fpf-agentic-thinking-map

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

1.9.5

2 files

1.9.4

2 files

1.9.3

2 files

1.9.2

2 files

1.9.1

2 files

This release

1.6.0 This release

2 files

1.5.0

2 files

1.4.25

2 files

1.4.24

2 files

1.4.23

2 files

1.4.22

2 files

1.4.21

2 files

1.4.20

2 files

1.4.19

2 files

1.4.18

2 files

1.4.17

2 files

1.4.16

2 files

1.4.15

2 files

1.4.14

2 files

1.4.13

2 files

1.4.12

2 files

1.4.11

2 files

1.4.10

2 files

1.4.9

2 files

1.4.8

2 files

1.4.7

2 files

1.4.6

2 files

1.4.5

2 files

1.4.4

2 files

1.4.3

2 files

1.4.2

2 files

1.4.1

2 files

1.4.0

2 files

1.3.1

2 files

1.3.0

2 files

1.2.1

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page