This release is a pre-release and may not be stable for production use.
hier-config-ai
Network configuration remediation driven by a language model, built on hier-config and PydanticAI.
What it is for
hier-config resolves most configuration differences deterministically. A few it cannot, and its custom workflows guide shows how those are handled today: inspect the default remediation, decide it is wrong, and build the correct one yourself in Python.
This library lets you describe the requirement instead.
The canonical case
An access list needs a new entry ahead of an existing one, so the existing entry must move from sequence 12 to 20. hier-config produces the right end state:
ip access-list extended TEST
no 12 permit ip 10.0.0.0 0.0.0.7 any
10 permit ip 10.0.1.0 0.0.0.255 any
20 permit ip 10.0.0.0 0.0.0.7 any
Between the removal and the re-add, though, the list matches nothing and an implicit deny drops live traffic. Avoiding that is what the manual workflow exists for — a temporary allow-all, a renumbering loop, a cleanup, written in Python and maintained per access list.
Described as a rule instead:
acl_rule = AIRemediationRule(
description=(
"Rewrite the access list so its entries end up with the intended "
"sequence numbers.\n"
"An entry cannot be renumbered in place. Delete it by number with "
"'no <seq>', then add it back at its new number.\n"
"The list must never deny live traffic while it is being "
"rewritten. Add '1 permit ip any any' as the first command, and "
"remove it with 'no 1' as the last."
),
lineage=(MatchRule(startswith="ip access-list"),),
example=AIRemediationExample(
running_config="ip access-list extended EXAMPLE\n 15 permit ip any any",
remediation_config=(
"ip access-list extended EXAMPLE\n"
" 1 permit ip any any\n"
" no 15\n"
" 10 permit ip 192.0.2.0 0.0.0.255 any\n"
" 20 permit ip any any\n"
" no 1"
),
),
)
Which produces:
ip access-list extended TEST
1 permit ip any any
no 12
10 permit ip 10.0.1.0 0.0.0.255 any
20 permit ip 10.0.0.0 0.0.0.7 any
no 1
The rule matches ip access-list generally rather than one list by name.
examples/ollama_acl.py runs this against a local Ollama model, verified with
qwen2.5-coder:7b on a laptop — it does not need a frontier model.
The plan is verified, not trusted
The model's plan is applied to the running configuration and re-checked against the intended one. If it does not converge, the difference goes back to the model and it corrects itself.
rule -> prompt -> model -> plan
|
apply and re-diff
|
converged? -- no --> tell the model what is still wrong
|
yes
|
HConfig
That loop is the point of the library. A plan that reads well and still leaves the device misconfigured is worse than no plan at all.
The scaffolding above is understood: 1 permit ip any any followed by no 1 is
a pair whose net effect is nothing. Forgetting the cleanup is rejected — a
permit ip any any left in a live access list is a hole.
What it does not do for you
Convergence proves the end state, not the path. Leave the traffic-safety sentence out of the description and the model returns the same unsafe plan hier-config generates, which validation accepts because the end state matches. Ordering constraints have to be stated. That is prose rather than Python, but it is not inferred.
Only send what needs judgment. Deterministic remediation is correct for most configuration, and it is free, instant, and never wrong.
Install
pip install "hier-config-ai[anthropic]" # or [openai], [google], [bedrock]
Ollama, Azure OpenAI, and OpenRouter speak the OpenAI-compatible API, so they
use the openai extra.
Use
The rule above, with the surrounding scaffolding:
import asyncio
from hier_config import HConfig, Platform
from hier_config.models import MatchRule
from hier_config_ai import (
AIRemediationExample,
AIRemediationRule,
AIWorkflowRemediation,
)
running = HConfig.from_text(Platform.CISCO_IOS, open("running.conf").read())
intended = HConfig.from_text(Platform.CISCO_IOS, open("intended.conf").read())
workflow = AIWorkflowRemediation(running, intended)
workflow.set_model("anthropic:claude-sonnet-4-5")
workflow.add_rule(acl_rule) # the AIRemediationRule shown above
remediation = asyncio.run(workflow.aai_remediation_config())
print("\n".join(remediation.to_lines()))
Add one rule per section that needs judgment. Rules run concurrently, so a
device with several of them costs one round trip rather than several.
aai_remediation_config() is the async form.
Reviewing a plan before you apply it
result = await agent.run(prompt, deps=deps)
plan = result.output
plan.plan # the commands
plan.reasoning # why the model chose them
plan.confidence # "high" | "medium" | "low"
plan.commands_requiring_review # commands that can cut reachability
Commands that would reload or wipe the device are rejected outright and never
reach you. Commands that are risky but legitimate are listed in
commands_requiring_review rather than blocked.
Choosing a model
Any PydanticAI model name works:
workflow.set_model("anthropic:claude-sonnet-4-5")
workflow.set_model("openai:gpt-4.1")
workflow.set_model("google-gla:gemini-2.0-flash")
workflow.set_model("bedrock:anthropic.claude-sonnet-4-5-20250929-v1:0")
For a self-hosted model, build the model object yourself and pass it to
set_agent(build_agent(model)).
Caching and rate limiting
from hier_config_ai import ResponseCache, RateLimiter
workflow.set_model(
"anthropic:claude-sonnet-4-5",
cache=ResponseCache(ttl_seconds=3600),
rate_limiter=RateLimiter(max_requests=60, time_window_seconds=60),
)
Both wrap the model rather than the client, so they cover tool calls and retries as well as the first request. Cached payloads contain device configurations, so the cache directory is created private to your user.
Failover and consensus
For ordered failover across providers, use PydanticAI directly:
from pydantic_ai.models.fallback import FallbackModel
workflow.set_agent(build_agent(FallbackModel("anthropic:claude-sonnet-4-5", "openai:gpt-4.1")))
To ask several models the same question and accept only an answer they agree
on, use consensus_plan(). Votes are counted on parsed configuration, so two
models that write the same configuration differently still agree.
Evaluating changes
Retrieval, prompt edits, and model changes can make output worse as easily as
better. evals/ measures whether plans actually converge:
poetry install --with dev,evals --all-extras
poetry run python evals/run_evals.py --model anthropic:claude-sonnet-4-5
This calls a real provider and costs money, so it is not part of CI.
Development
poetry install --with dev --all-extras
poetry run python scripts/build.py lint-and-test
See CONTRIBUTING.md.
License
Apache 2.0. See LICENSE.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file hier_config_ai-0.2.1a0.tar.gz.
File metadata
- Download URL: hier_config_ai-0.2.1a0.tar.gz
- Upload date:
- Size: 33.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
poetry/2.4.1 CPython/3.12.14 Linux/6.17.0-1022-azure
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ebdbd584eda55d557debd21ff9a5333260f84ecff999034a270ef125b1e31cf1
|
|
| MD5 |
9b2b6b1c8e06e39a92b6a6799fa1c8ca
|
|
| BLAKE2b-256 |
f444796208e23d75835b4bd700f001752c209f47904f6151b83d158aa6f227b4
|
File details
Details for the file hier_config_ai-0.2.1a0-py3-none-any.whl.
File metadata
- Download URL: hier_config_ai-0.2.1a0-py3-none-any.whl
- Upload date:
- Size: 36.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
poetry/2.4.1 CPython/3.12.14 Linux/6.17.0-1022-azure
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
80fba3acefbd19d8214bbaac8a637957e52d87decf4e070775a97f821fdc5350
|
|
| MD5 |
625384a970650cc88c8ab84818facbeb
|
|
| BLAKE2b-256 |
e24b5c9a9068ec1f15c963f30fbff7ee4f7898aa6f2ee9b51b5f41c2ec7fe21f
|