Skip to content
DaurenAmanbayevPublic

About

Governed investigative-agent skeleton — a domain-agnostic, hexagonal core for auditable, privacy-aware LLM agents. HMAC hash-chained audit, fail-closed governance, LLM-boundary pseudonymization, budget/approval gates. Specialize via three ports; no network needed to test.

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

agentkit

A governed investigative-agent skeleton — the domain-agnostic core delaminated from a production network-diagnostics assistant so it can be re-skinned for any vertical: devops, finance, support triage, compliance, ops.

An agent investigates a target, gathers evidence with read-only tools, and finalizes ranked hypotheses with confidence — under budget/iteration/timeout caps, with write operations gated behind human approval, every run recorded in an HMAC hash-chained append-only audit log, and sensitive identifiers pseudonymized at the LLM boundary. None of that machinery knows anything about your domain; you supply the domain through three small ports.

                    ┌───────────────── agentkit core ─────────────────┐
   your vertical →  │  loop · budget · audit · profiles · pseudonymizer │
   (a DomainPack)   │  tool framework · LLM runtime · approval gate     │
                    └───────────────────────────────────────────────────┘
                         ▲ depends on              ▲ depends on
                         │  the three ports        │  (never the reverse)

Positioning — a skeleton, not a framework

agentkit is a small, opinionated core, not a batteries-included framework. It owns one control flow — gather evidence with tools → finalize ranked hypotheses — under governance (budget/approval/audit/pseudonymization), and nothing else. It deliberately ships no built-in providers, no persistence layer, no agent marketplace, no convergence/execution engine.

Reach for agentkit when you are building an investigative / diagnostic agent (incident triage, SRE/NetOps, compliance checks, support deflection, fraud review) and you need auditability, privacy, and fail-closed governance from day one — especially in regulated or on-prem/sovereign settings. The HMAC hash-chained audit, the pseudonymization-at-the-LLM-boundary with a fail-closed cloud perimeter, and the declarative per-tenant tool policy are the reason to choose it over a general framework.

Reach for something else (LangChain, LlamaIndex, an agent SDK) when you want a broad toolbox of integrations, RAG/chains/memory out of the box, or a general chat/coding agent. agentkit is narrower by design; it expects you to bring your domain through the three ports and to layer persistence/observability as your own concern (hooks are provided — see RunListener).

What it gives you that general frameworks treat as afterthoughts: governance is test-locked and fails closed, the public API is a semver contract, and the whole loop runs without a network (the LLM backend is injectable) so it is testable and sovereign-deployable. Execution/convergence (DesiredState → apply → rollback) is intentionally out of scope — that is a separate concern a consumer layers on; the governance primitives are reusable standalone for exactly that (see govern_tool_call).

The three ports

A vertical fills these in; the core reads them. Dependency points one way: skin → core.

Port What it replaces What you provide
DomainPack the network prompt + triage + vocab system prompt, core_tool_patterns (kept when narrowing), vocab_stems (non-English relevance), pii_terms(), session_prep() opener, and a resolver
ProviderSlot the hardcoded 5 network slots register_provider(slot, impl) — backends your tools call
ResourceResolver device name→management-IP resolution resolve(target_id, type) — name→address for connects_by_target tools

Plus @register_tool for the tools themselves (vendor-neutral domain.action names, metadata — category/weight/risk/write/visibility_gate — declared at the registration site), and a target: {id, type} that replaces the network device.

Quick start

python3.12 -m pip install -e ".[dev,llm,api,docs]"
make ci                                          # lint + types + tests + docs + dependency audit

Build a vertical — the step-by-step Writing a skin guide walks from the reference examples/echo_domain/ (a key/value "facts catalog" in ~120 lines) to a real one, with the full port contracts. The short version:

from agentkit import DomainPack, register_tool, register_provider, set_domain, ToolResult

register_provider("store", MyBackend())

@register_tool(name="facts.lookup", description="Read a target's facts.",
               provider_slot="store", weight="light", connects_by_target=True)
def lookup(target: str) -> ToolResult:
    return ToolResult.ok(get_provider("store").facts[target], source_ref=f"store:{target}")

class MyDomain(DomainPack):
    name = "facts"
    system_prompt = "You are a catalog investigator. Use facts.lookup, then finalize."
    core_tool_patterns = ("facts.lookup",)

set_domain(MyDomain())

Run it:

agentkit run node-alpha --type node --domain examples.echo_domain:install
agentkit profile validate          # fail-closed check of config/profile.yaml
agentkit audit-verify --db data/audit.db   # checksums + chain + seq + tail-truncation

Or serve the authenticated reference HTTP adapter (the api extra). Configure the bearer token and persistent audit path first:

export AGENTKIT_API_BEARER_TOKEN='replace-me'
export AGENTKIT_AUDIT_HMAC_KEY='replace-me-too'
export AGENTKIT_DB_PATH='data/audit.db'
uvicorn agentkit.surfaces.api:app     # POST /v1/run · GET /v1/audit/verify · /healthz · /metrics
curl -H "Authorization: Bearer $AGENTKIT_API_BEARER_TOKEN" \
  -H "Content-Type: application/json" -d '{"target":"node-alpha","type":"node"}' \
  http://127.0.0.1:8000/v1/run

/metrics renders Prometheus text exposition (runs/iterations/cost/tool-calls). See docs/HTTP_API.md before exposing this adapter outside a trusted development network.

In code:

from agentkit import Session, Target, run_session, AuditLog
result = await run_session(
    Session(run_id="r1", target=Target(id="node-alpha", type="node")),
    approval_fn=my_approval_callback,        # async (payload) -> bool, gates write tools
    audit=AuditLog("data/audit.db"),
)

What's harvested (and what isn't)

The core is a faithful, generalized lift of the upstream product's domain-agnostic modules — see HARVEST.md for the per-module provenance and exactly what was generalized. The network skin (providers, prompts, composites, enrichment topology, vendor backends) was not harvested; it is rebuilt per domain.

Guarantees carried over as framework invariants (locked by tests):

  • Frozen models — model_copy(update=...), never mutate.
  • Append-only audit — BEFORE UPDATE/DELETE triggers; HMAC hash-chain + monotonic seq.
  • Write ops require approval — the gate runs before registry lookup.
  • Allow-list defaults to all (empty = inherit), profiles fail closed.
  • Pseudonymization — typed bijective reversible tokens; off | cloud | always.
  • Core imports no skin — tests/test_seam.py enforces the one-way dependency.

Security & operations

The skeleton is secure when configured, and tells you loudly when it is not. Knobs (all CoreSettings, env-overridable; see config/profile.yaml.example):

Concern Setting Hardening
Tamper-evident audit AGENTKIT_AUDIT_HMAC_KEY HMAC-SHA256 chain. Absent → a loud warning; checksums fall back to plain SHA-256 (integrity only).
Mandatory trail AGENTKIT_AUDIT_REQUIRED=true a failed audit append aborts the run (AuditUnavailableError) instead of being swallowed.
Tail-truncation (automatic) a sidecar high-water-mark detects dropped/last-rewritten rows that the chain alone cannot.
PII to cloud LLMs AGENTKIT_LLM_PSEUDONYMIZE=cloud unknown model/host = cloud; set AGENTKIT_LLM_IS_LOCAL=true only for a trusted on-prem backend.
Secret leakage (automatic) tool output and audit payloads are redacted (PEM keys, JWTs, URL creds, AWS keys, key=secret).
Domain code-exec AGENTKIT_DOMAIN_ALLOWLIST=examples.,mycorp. gates the --domain module:callable import surface to listed prefixes.
HTTP authentication AGENTKIT_API_BEARER_TOKEN protected endpoints fail with 503 until configured; insecure mode is explicit and local-only.
HTTP tenant scope AGENTKIT_API_TENANT_ID tenant policy is selected server-side; request JSON cannot choose it.
HTTP rate limit AGENTKIT_RATE_LIMIT_PER_MIN process-local protection; use a gateway for distributed enforcement.

All settings are AGENTKIT_-prefixed env vars (collision-safe), or fields on CoreSettings. | Structured logs | configure_logging() | opt-in JSON/console structlog with run_id/tenant bound on every run. |

The package ships type information (py.typed); the loop's injection seams are typed via Protocols in agentkit/protocols.py.

Testing without an LLM

agentkit.set_completion_fn(fake) injects the completion backend, so the whole loop runs deterministically with a scripted fake LLM and no network (litellm is optional). See tests/conftest.py FakeLLM.

Public API & releases

The public API is exactly agentkit.__all__ — what from agentkit import … exposes. Anything reached by a submodule path (agentkit.tool_adapter.execute_tool, …) is internal and may change without a major bump. tests/test_public_api.py locks the surface so it can only move deliberately. Versioning is semantic; removals go through a @deprecated cycle. Consumers pin a git tag until the package is published to an index:

dependencies = ["agentkit @ git+https://github.com/<org>/agentkit.git@v0.3.0"]

See docs/RELEASING.md for the contract, the semver/deprecation policy, and the release steps (make build, make release-check). A browsable API reference is generated from docstrings with make docs (the docs extra: MkDocs + mkdocstrings).

Layout

agentkit/
├── agentkit/                  # the framework (domain-agnostic)
│   ├── models.py tools.py ports.py config.py     # data + the three ports + settings
│   ├── loop.py tool_adapter.py                    # the agent loop + schema/gate
│   ├── llm.py audit.py profiles.py                # LLM runtime · audit chain · profiles
│   ├── pseudonymizer.py redact.py                 # LLM-boundary privacy
│   ├── protocols.py logging.py metrics.py         # typed seams · log config · metrics
│   └── surfaces/cli.py surfaces/api.py            # CLI + HTTP (FastAPI) surfaces
├── examples/echo_domain/      # reference skin proving the three ports
├── Dockerfile ARCHITECTURE.md # container image · design doc
├── .github/workflows/ci.yml   # ruff + mypy + pytest on 3.12 / 3.13
└── tests/                     # pytest (ruff + mypy clean, coverage ≥ 90%)

About

Governed investigative-agent skeleton — a domain-agnostic, hexagonal core for auditable, privacy-aware LLM agents. HMAC hash-chained audit, fail-closed governance, LLM-boundary pseudonymization, budget/approval gates. Specialize via three ports; no network needed to test.

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages