Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Task 8: Solution and What Comes Next

Solution to Task 7

Show the full program and what it prints
"""Drive a small family knowledge base from Python."""

from framex import Client, FrameXError

PROGRAM = """world open.

Person { hasName: String }.
Man extends Person.
Woman extends Person.

elena : Woman.
anna : Woman.
bruno : Man.
dario : Man.
felix : Man.

anna[hasParent -> elena].
bruno[hasParent -> elena].
dario[hasParent -> bruno].
felix[hasParent -> anna].

?X[hasSibling -> ?Y] <- ?X[hasParent -> ?P] AND ?Y[hasParent -> ?P] AND ?X != ?Y.
?C[hasGrandparent -> ?G] <- ?C[hasParent -> ?P] AND ?P[hasParent -> ?G].
"""


def describe(result):
    status = result["status"]
    if status == "bindings":
        rows = result["bindings"]
        return f"{len(rows)} match(es): {rows}"
    return status


NEW_FACTS = """carla : Woman.
carla[hasParent -> elena].
"""


def ask(client, query):
    try:
        return describe(client.query(query))
    except FrameXError as error:
        return f"rejected ({error.error['kind']}): {error.error['message']}"


def main():
    with Client() as client:
        loaded = client.load(PROGRAM)
        print("loaded:", loaded)

        result = client.query("?- dario[hasGrandparent -> elena].")
        print("grandparent:", result)

        print("ground :", describe(client.query("?- dario[hasGrandparent -> elena].")))
        print("binding:", describe(client.query("?- dario[hasGrandparent -> ?G].")))
        print("open   :", describe(client.query("?- dario[hasParent -> anna].")))
        print("many   :", describe(client.query("?- ?X[hasSibling -> ?Y].")))

        change = client.add(source=NEW_FACTS)
        print("added  :", change["added"])
        print("removed:", change["removed"])
        print("carla  :", describe(client.query("?- carla[hasSibling -> ?Y].")))

        print()
        print(client.explain("dario[hasGrandparent -> elena]"))

        blocked = client.why_not("dario[hasParent -> anna]")
        print("why_not status:", blocked["nodes"][0]["status"])
        print("why_not scope :", blocked["scope"])

        print()
        print("dependents:", client.dependents("dario[hasParent -> bruno]"))

        removal = client.retract(fact="dario[hasParent -> bruno]")
        print("retracted :", removal["removed"])
        print("diff      :", client.diff()["removed"])
        print("after     :", describe(client.query("?- dario[hasGrandparent -> elena].")))

        print()
        switch = client.world("closed")
        print("closing removed:", switch["removed"])
        print("closed         :", describe(client.query("?- dario[hasGrandparent -> elena].")))

        report = client.validate()
        print("violations     :", report["violations"])

        print()
        print("bad query:", ask(client, "?- this is not FrameX"))

        print("commands :", [entry["command"] for entry in client.transcript])
        try:
            client.save_replay("session.jsonl")
            print("replay   : written to session.jsonl")
        except FileExistsError:
            print("replay   : session.jsonl already exists; delete it first")


if __name__ == "__main__":
    main()

Running it prints:

loaded: {'facts': 26}
grandparent: {'status': 'true'}
ground : true
binding: 1 match(es): [{'G': 'elena'}]
open   : unknown
many   : 2 match(es): [{'X': 'anna', 'Y': 'bruno'}, {'X': 'bruno', 'Y': 'anna'}]
added  : ['carla:Person', 'anna[hasSibling -> carla]', 'bruno[hasSibling -> carla]', 'carla[hasSibling -> anna]', 'carla[hasSibling -> bruno]']
removed: []
carla  : 2 match(es): [{'Y': 'anna'}, {'Y': 'bruno'}]

World assumption: open
dario[hasGrandparent -> elena]
  Derived by rule #1: ?C[hasGrandparent -> ?G] <- ?C[hasParent -> ?P] AND ?P[hasParent -> ?G].
  Bindings: C = dario, G = elena, P = bruno
  Because:
    dario[hasParent -> bruno]
      Asserted fact
        Source: <json-source> | time: unspecified | weight: None | <json-source>:15:1
    bruno[hasParent -> elena]
      Asserted fact
        Source: <json-source> | time: unspecified | weight: None | <json-source>:14:1

why_not status: unknown
why_not scope : bounded first-blocker inspection; not exhaustive minimal repairs

dependents: ['dario[hasGrandparent -> elena]']
retracted : ['dario[hasGrandparent -> elena]']
diff      : ['dario[hasGrandparent -> elena]']
after     : unknown

closing removed: []
closed         : false
violations     : []

bad query: rejected (SyntaxError): expected comparison operator
commands : ['load', 'query', 'query', 'query', 'query', 'query', 'add', 'query', 'explain', 'why_not', 'dependents', 'retract', 'diff', 'query', 'world', 'query', 'validate', 'query']
replay   : written to session.jsonl

More to Come

That is the core of the Python API: start a session, load a program, ask questions, grow and revise what the engine knows, make it explain itself, switch the world assumption, and keep a record of everything you sent. The Client page lists the methods in one table, including the ones this exercise did not use — compare, retract_source, rdf_import and rdf_export.

Continue with Tool Calling: rules derive a request, Python runs a registered tool, and its receipt returns to the rules. The Grounded Extraction example adds evidence-based acceptance and offline failure tests.

The hybrid pattern guide describes further designs, including query translation and candidate-rule checking. The public export has a smaller scope than the development framework; consult Sources and Version Scope before using a component from the handbook.