hermes.algebra
Algebra
A query is an immutable plan built one operator at a time. The explorer applies an action, runs the plan, and reports what changed.
Actions
Each action is a frozen dataclass with an apply(plan) method, written as one JSON object. parse_action turns the JSON into a typed command. Source: algebra.py.
| Action | SPARQL algebra | Example |
|---|---|---|
triple | BGP, join | {"op": "triple", "pattern": "?c ex:hosted ?g"} |
filter | Filter | {"op": "filter", "expr": "?year > 1900"} |
optional | LeftJoin | {"op": "optional", "patterns": ["?c ex:country ?k"]} |
union | Union | {"op": "union", "branches": [[...], [...]]} |
minus | Minus | {"op": "minus", "patterns": [...]} |
bind | Extend | {"op": "bind", "expr": "YEAR(?date)", "var": "?y"} |
group, aggregate | Group, Aggregation | {"op": "aggregate", "expr": "COUNT(?g)", "alias": "?n"} |
having | Filter on groups | {"op": "having", "expr": "?n > 2"} |
order | OrderBy | {"op": "order", "expr": "DESC(?n)"} |
project, distinct | Project, Distinct | {"op": "project", "vars": ["?c"]} |
limit, offset | Slice | {"op": "limit", "n": 1} |
ask | ASK form | {"op": "ask"} |
remove | retract a triple or filter | {"op": "remove", "text": "?c ex:country ?k"} |
Two actions read the graph and leave the plan alone: lookup finds entities whose label contains a text, and inspect lists the predicates around an entity, or used by the instances of a class, with counts and an example value. Control actions are goal (see Verifier), undo, and finish.
render(plan, header) is the builder from a plan to SPARQL. It is deterministic.
The explorer
from hermes.algebra import Explorer, parse_action
explorer = Explorer(kg)
explorer.run(parse_action({"op": "triple", "pattern": "?city ex:hosted ?g"}))
explorer.preview(parse_action({"op": "triple", "pattern": "?g ex:season \"Winter\""}))
print(explorer.sparql)
run commits a plan-changing action only if the new plan parses, passes the verifier when one is installed, and runs. preview returns the same observation without committing. undo restores the previous plan. Explorer(kg, sample_size=5, kernel=None); pass a kernel to type-check every step.
Observations
After each step the explorer runs two queries: the number of solutions, as a COUNT over the plan used as a subquery, and a sample of rows under LIMIT. An ASK plan reports its Boolean. Observation.summary() is the line the planner reads:
{"op": "triple", "pattern": "?city ex:hosted ?g"} -> 19 solutions
{"op": "triple", "pattern": "?city ex:country ex:USA"} -> 19 -> 8 solutions
{"op": "triple", "pattern": "?g ex:season \"Winter\""} -> 8 -> 4 solutions
{"op": "distinct"} -> 4 -> 3 solutions
A rejected step reads rejected: ... with the parser, verifier, or store message. Executions are memoized by the store cache.
Policies
Any policy can choose the actions. The algebra-agent system is an LLM policy: each turn it sees the action catalogue, the vocabulary, linked entities, every earlier action and observation, and the current query, and replies with one action. It stops on finish or after max_steps (15), refuses an action identical to the previous one, and returns the last committed plan that runs. Source: algebra_agent.py. UniQGen's Chase and Backchase is, in these terms, a beam search over remove and triple.