{"slug": "show-hn-actionrail-runtime-value-action-grounding-framework-for-ai-agents", "title": "Show HN: ActionRail, Runtime value/action grounding framework for AI agents", "summary": "ToolJet released ActionRail, an open-source runtime value and action grounding framework for AI agents, now in public beta with a planned v1 for August 2026. ActionRail intercepts agent tool calls and validates each argument against live system data before execution, returning allow, hold-for-human, or block decisions to prevent semantically wrong operations such as duplicate refunds or cross-customer transfers. The framework-neutral Python SDK supports explicit integration via ActionRuntime and automatic tool discovery for LangGraph agents.", "body_md": "ActionRail is an open-source project by [ToolJet](https://www.tooljet.com).\n\nRelease status:ActionRail is currently in public beta. Interfaces may evolve before ActionRail v1, which is planned for August 2026.\n\nIntegration support:Any Python application can use the framework-neutral`ActionRuntime`\n\n. LangGraph also has automatic tool discovery and wrapping; additional automatic framework adapters are planned.\n\nAn agent's tool call can be perfectly *permitted* and perfectly *well-formed* and\nstill be *wrong*: a refund on an order that was already refunded, a transfer to a\nreal account that belongs to the wrong customer, a withdrawal larger than the\nbalance. Allowlists and schema checks pass all of these, because the *shape* is\nfine — it's the *value* that's wrong.\n\nActionRail intercepts an agent's consequential tool calls and, before execution,\ngrounds each argument **against your live system of record** — then returns\n**allow / hold-for-human / block**. It's the last-mile correctness gate: not \"is\nthe agent allowed to do this?\" but \"is this specific value actually right, right\nnow, against your real data?\"\n\n*ActionRail runs at the execution boundary. Source lookups and credentials stay\nin the customer environment, and the tool runs only after an allow decision.*\n\nThe ActionRail runtime is framework-neutral. LangGraph is the first adapter that adds automatic tool discovery and wrapping.\n\n| Your application | Integration | Start here |\n|---|---|---|\n| Any Python agent, worker, API, or application | Explicit `ActionRuntime` boundary |\n|\n\n[LangGraph quickstart](https://actionrail.ai/docs/getting-started/quickstart/)Install the base SDK, create an agent and its actions in the Console, then put\n`ActionRuntime`\n\naround the real operation:\n\n```\npython -m pip install actionrail actionrail-console\npython\nfrom actionrail import ActionRuntime\n\nruntime = ActionRuntime(\n    agent_id=\"…\",\n    api_key=\"ark_…\",\n)\n\nresult = runtime.execute(\n    \"issue_refund\",\n    {\"order_id\": order_id, \"amount\": amount},\n    issue_refund,\n    context={\"customer_id\": authenticated_customer_id},\n)\n```\n\n`execute()`\n\ninvokes `issue_refund(**arguments)`\n\nonly after authorization. Use\nthe same API in custom agent loops, workers, API handlers, or frameworks without\na dedicated adapter. See the [direct Python integration](https://actionrail.ai/docs/sdk/framework-neutral/).\n\nUse `enforce()`\n\nwhen you want ActionRail to discover and wrap a compiled\nLangGraph agent's tools:\n\n``` python\nfrom actionrail import enforce, trusted_context\nfrom actionrail.sdk.grounding import SQLiteSource\n\nagent = enforce(\n    agent,\n    agent_id=\"…\",\n    api_key=\"ark_…\",\n    sources={\"billing\": SQLiteSource(\"billing.db\")},\n)\nwith trusted_context(customer_id=\"C-1007\"):\n    agent.invoke(inputs)\n# wire_money(account=\"1234567\") -> blocked before the underlying tool runs\n```\n\nDetailed reasons remain available to local application code through the\n`on_decision`\n\ncallback. The model-facing tool result, outbound Activity, and\nreview records use separate value-free representations.\n\nThe complete deterministic demo needs no model API key:\n\n```\npython3 -m venv .venv\nsource .venv/bin/activate\npython -m pip install \"actionrail[agents]\" actionrail-console\nactionrail-console --no-open\n```\n\nIn a second terminal:\n\n```\nsource .venv/bin/activate\nactionrail-quickstart\n```\n\nThe demo creates a local SQLite Source, installs a grounding rule, attempts a\ncross-customer refund, proves the real tool did not execute, and verifies the\nredacted block in Activity. Open `http://127.0.0.1:8020/activity`\n\nto inspect it.\n\nTo run the same poisoned support note through a real model-driven LangGraph loop, provide an Anthropic API key and a model ID available to your account:\n\n```\nexport ANTHROPIC_API_KEY=\"…\"\nactionrail-quickstart --model claude-sonnet-4-6\n```\n\nThe demo tool is local and harmless. If the model proposes the cross-customer refund, ActionRail verifies the order against authenticated caller context and blocks it before the function runs. If the model resists the injected note, the command reports that outcome and submits the same poisoned proposal as a clearly labelled deterministic runtime challenge. This tests the ActionRail boundary without pretending the proposal came from the model. The deterministic mode remains the stable path for CI and first-time evaluation.\n\nPolicy engines and authz (OPA, Cedar, ReBAC) decide whether an action is\n*permitted* given facts you've pre-loaded. By design they don't reach out to a\nlive system and check a value at call time — Cedar, for instance, is a\nside-effect-free language with no I/O. ActionRail fills exactly that gap: it\nqueries your real data at the moment of the call and verifies the argument.\n\n| Check | Answers | ActionRail |\n|---|---|---|\n| Allowlist / schema | is the call well-formed? |\n— |\n| Policy / authz | is the agent permitted? |\n— |\nGrounding |\nis the value correct vs. live data? |\n✅ |\n\nWe red-teamed eight models across four providers with value-poisoning attacks:\ninputs that steer an agent toward a well-formed but *wrong* argument value. Every\nmodel executed at least one poisoned value when unprotected, with attack success\nranging from **1.7% to 63.3%** — a stronger model did not make the risk go away.\nWith ActionRail in front, **0 of 480** manipulated actions executed and **0 of\n480** legitimate look-alike requests were wrongly blocked.\n\n** Read the value-poisoning benchmark →** ·\n\n`decide()`\n\nruns cheapest-check-first and returns `allow`\n\n/ `hold`\n\n/ `block`\n\n(block outranks hold outranks allow):\n\n| Tier | Check | Example |\n|---|---|---|\nPolicy |\ndeterministic expression | `amount <= 200` → over limit holds for a human |\nGrounding |\nvalue vs. system of record | account must exist, belong to the caller, and be in a refundable state → mismatch blocks |\n\nGrounding is expressive: multiple checks ANDed across different Sources, each\ncomparing a returned field with an operator (`= ≠ > ≥ < ≤ contains, is one of, is set, is empty`\n\n) against a caller-context value, a literal, **another argument**\n(`balance ≥ amount`\n\n), or the **current time** (`delivered_at ≥ now-30d`\n\n). Checks\ncan require a row (`expect: row`\n\n) or its *absence* (`expect: absent`\n\n, for\nblocklists / idempotency), bind query params by name (`:customer_id`\n\nin SQLite,\n`%(customer_id)s`\n\nin PostgreSQL/MySQL), and carry a retry + fail-open/closed policy.\n\nCommit expected action behavior alongside your rules and run it through the same decision pipeline used in production:\n\n```\n# actionrail.cases.yaml\nschema_version: 1\ncases:\n  - name: cross-customer refund is blocked\n    tool: issue_refund\n    args: {order_id: order-100, amount: 75}\n    context: {customer_id: customer-2}\n    expect: block\n    fixtures:\n      billing-production:\n        by_value:\n          order-100: {customer_id: customer-1, status: delivered}\nactionrail test\n# PASS  cross-customer refund is blocked\n# 1 passed · 0 failed\n```\n\n**Action Tests** make `allow`\n\n, `hold`\n\n, and `block`\n\na reviewable CI contract.\nDeterministic Source fixtures exercise the real policy evaluator and grounding\nmatcher without a model, Console, credentials, or network access. Production\nSources remain off unless a separate integration stage explicitly passes\n`--live-sources`\n\n. See the [Action Tests guide](/ToolJet/ActionRail/blob/master/docs/docs/testing/action-tests.mdx)\nor run the complete contract in [ examples/action_tests/](/ToolJet/ActionRail/blob/master/examples/action_tests).\n\nGrounding runs against sources that stay in your environment (the data plane\nnever leaves your VPC). Today: **SQLite**, **PostgreSQL**, **MySQL/MariaDB**,\n**HTTP/REST**, and **MCP gateway** adapters. MCP Sources call a configured verification tool over\nStreamable HTTP, require its `readOnlyHint`\n\n, and prefer structured tool output. Secrets are\nwritten as `${env:VAR}`\n\nreferences and resolve locally, so credentials never\nreach the control plane. Use a gateway identity that can invoke only read tools;\nMCP annotations are hints, not an authorization boundary.\n\n```\nsources:\n  stripe-production:\n    adapter: mcp\n    endpoint: https://gateway.internal/mcp\n    headers:\n      Authorization: Bearer ${env:MCP_GATEWAY_TOKEN}\n    tool: stripe.get_charge\n    arguments:\n      charge_id: \"{value}\"\n    select: data.charge\n    timeout: 10\n    require_read_only: true\n```\n\nThis keeps MCP connection details on the Source. Rules reference the Source and only describe how to evaluate the returned record; they do not duplicate the gateway endpoint, tool name, or argument mapping.\n\nPostgreSQL Sources keep connection details on the Source and queries on rules.\nUse a dedicated role with `SELECT`\n\n-only permissions. ActionRail also forces each\nverification transaction into PostgreSQL read-only mode and applies connection\nand statement timeouts. Queries use Psycopg named parameters such as\n`%(value)s`\n\n, `%(amount)s`\n\n, and `%(customer_id)s`\n\n:\n\n```\nsources:\n  billing-production:\n    adapter: postgres\n    host: postgres.internal\n    port: 5432\n    dbname: billing\n    user: actionrail_reader\n    password: ${env:POSTGRES_PASSWORD}\n    sslmode: verify-full\n    sslrootcert: /etc/ssl/certs/postgres-ca.pem\n    connect_timeout: 5\n    statement_timeout_ms: 5000\n    require_read_only: true\n```\n\nThe SDK depends on the Psycopg interface, leaving the libpq implementation to\nthe host application. For a self-contained local install, add\n`psycopg[binary]>=3.2,<4`\n\n; production environments can use the system-linked\n`psycopg[c]`\n\nbuild instead.\n\nMySQL and MariaDB Sources use the same named query parameters and enforce\n`START TRANSACTION READ ONLY`\n\nfor every check. PyMySQL is included with the SDK:\n\n```\nsources:\n  billing-mysql:\n    adapter: mysql\n    host: mysql.internal\n    port: 3306\n    database: billing\n    user: actionrail_reader\n    password: ${env:MYSQL_PASSWORD}\n    ssl_mode: verify-identity\n    ssl_ca: /etc/ssl/certs/mysql-ca.pem\n    connect_timeout: 5\n    query_timeout: 5\n    require_read_only: true\n```\n\nAn optional Django + DRF control plane and React Console let you register\nsources, author grounding rules per argument, and watch a live **Activity\nfeed** of every allow / hold / block the runtime made. The SDK reports metadata\nonly — tool names, outcomes, value-free reasons, and redacted previews. Detailed\ngrounding results and rendered previews are exposed only on the local `Decision`\n\n;\nmodel-facing tool results receive their own fully redacted representation. Preview\nplaceholders in control-plane reports become `[redacted]`\n\nunless a rule explicitly\nallowlists a field with `write.report_args`\n\n; source-record values are never exported. Runtime reports use\na bounded, local SQLite outbox with batching, retry backoff, saturation\nbackpressure, and a normal-shutdown flush. Events are committed before the SDK\naccepts them, stable IDs make retries idempotent, and delivery resumes after a\nprocess restart. The outbox contains metadata but never the agent key and is\nstored under `~/.actionrail/sdk/`\n\nby default; set `ACTIONRAIL_SDK_STATE_DIR`\n\nor\npass `state_dir=`\n\nto choose another location. Call `close_reporting(agent)`\n\nfrom\nyour application shutdown hook. A `False`\n\nresult means rows remain durably\npending for the next process rather than being discarded.\n\nArgument reporting is explicit and field-level; everything else remains redacted:\n\n```\nwrite:\n  preview: \"Refund order {order_id} for {amount}\"\n  report_args: [amount]  # explicit opt-in; order_id remains [redacted]\n```\n\nExpose `get_runtime_health(agent)`\n\nfrom your application health endpoint to see\nwhether the SDK is using cached configuration, how stale its last validated\nsnapshot is, and whether audit rows are pending after a delivery failure.\n\nHuman-review actions always fail closed when the Console cannot be reached. The\nmodel-facing result uses `ACTIONRAIL_REVIEW_UNAVAILABLE`\n\n, distinct from a normal\npending review or human rejection, and the underlying tool is never executed.\n\nRemote enforcement accepts cached configuration for up to 24 hours by default.\nAfter `max_config_staleness`\n\nis exceeded, enforcing-mode tool calls fail closed with\n`ACTIONRAIL_CONFIG_STALE`\n\nuntil a refresh succeeds. Set the limit in seconds on\n`enforce(...)`\n\n; pass `None`\n\nonly when your deployment explicitly accepts\nunbounded configuration staleness.\n\nThe OSS Console and SDK send three allowlisted events to\n`https://hub.actionrail.ai/v1/events`\n\n: `console_started`\n\n, `sdk_initialized`\n\n, and\none `usage_summary`\n\nsnapshot per UTC day. The summary contains exact totals\nfor configured and active agents, observed and evaluated actions, and actions\nmonitored, allowed, blocked, or held. Events also contain a locally generated\nanonymous installation UUID, package version, Python major/minor version, and\noperating-system family. They never contain agent or tool identities, rules,\narguments, individual decisions, Source configuration, endpoints, credentials,\naudit records, paths, or errors.\n\nDisable telemetry before starting the Console or agent process:\n\n```\nexport ACTIONRAIL_TELEMETRY_DISABLED=1\n```\n\n`DO_NOT_TRACK=1`\n\nand `CI=1`\n\nare also honored. Delivery has a one-second timeout\nand cannot affect enforcement or startup. Lifecycle events are best-effort. The\nConsole persists at most one aggregate-only usage summary for an hourly retry;\nit never queues individual activity. See [Anonymous telemetry](/ToolJet/ActionRail/blob/master/docs/docs/security/telemetry.mdx)\nfor the complete event schema, counter definitions, and identifier lifecycle.\n\nSee [Runtime availability and failure behavior](/ToolJet/ActionRail/blob/master/docs/docs/operations/runtime-availability.mdx)\nfor the complete startup, outage, recovery, review, and shutdown contract.\n\nContributors using an editable repository checkout can run the packaged\nquickstart implementation through `python examples/langgraph_quickstart.py`\n\n.\n\nFor development and the full test suite:\n\n```\nuv pip install -e \".[dev,control-plane]\"   # SDK + tests + control plane\npytest                                     # run the suite\n```\n\nTo run the development Console:\n\n```\npython control-plane/manage.py migrate && python control-plane/manage.py runserver 8020\ncd frontend && npm install && npm run dev   # http://localhost:5173\n```\n\nTo build the two beta wheels for manual distribution:\n\n```\npython scripts/build_beta_wheels.py\n# dist/actionrail-0.1.0b9-py3-none-any.whl\n# dist/actionrail_console-0.1.0b9-py3-none-any.whl\n# dist/SHA256SUMS\n```\n\nInstall both wheels, then start the self-contained local Console:\n\n```\npython -m pip install dist/actionrail-*.whl dist/actionrail_console-*.whl\nactionrail-console\n```\n\nThe Docusaurus site lives in `docs/`\n\n:\n\n```\ncd docs\nnpm ci\nnpm start\n```\n\nRun the production build and broken-link checks with:\n\n```\nnpm run build\n```\n\nSee [ docs/README.md](/ToolJet/ActionRail/blob/master/docs/README.md) for deployment variables, optional Algolia\nsearch, site structure, and contribution conventions.\n\n```\nactionrail/sdk/\n  runtime.py            # framework-neutral evaluate / execute boundary\n  actions.py            # explicit, privacy-safe action definitions\n  action_tests.py        # versioned allow / hold / block regression contracts\n  scanner.py            # zero-config action-surface discovery on a LangGraph agent\n  instrument.py         # report the surface + observed calls to the control plane\n  enforce.py            # the runtime gate: wrap consequential tools, decide before they run\n  pipeline.py           # policy -> grounding -> allow / hold / block\n  policy.py             # tier-1 deterministic policy\n  config.py             # rule config + Source adapter build (with ${env:VAR} resolution)\n  report.py             # metadata-only client to the control plane\n  safewrite.py          # dry-run preview + transactional rollback (reversible sinks)\n  grounding/\n    base.py             # Source protocol + GroundVerdict\n    match.py            # shared condition matcher (operators, ctx/arg/now targets)\n    sqlite.py           # SQLite source adapter\n    http.py             # HTTP/REST source adapter\n    mcp.py              # MCP gateway source adapter (read-only tools)\n    postgres.py         # PostgreSQL source adapter (read-only transactions)\n    mysql.py            # MySQL/MariaDB source adapter (read-only transactions)\nactionrail/cli.py        # `actionrail test` framework CLI\ncontrol-plane/          # Django + DRF: agents, rules, sources, decision feed\nfrontend/               # React console\ntests/                  # pytest suite (grounding, pipeline, domains, SDK, control plane)\nexamples/               # runnable deterministic integrations\n```\n\n**Public beta:** the framework-neutral `ActionRuntime`\n\n, LangGraph discovery and\n`enforce()`\n\nadapter, policy + grounding pipeline, deterministic Action Tests,\nSQLite + PostgreSQL + MySQL + HTTP + MCP Sources, control plane, and Console are\nimplemented and covered by the test suite.\n\n**Roadmap:** provenance and taint tracking for values from untrusted memory,\nadditional agent-framework integrations, and more vendor-specific Source\nadapters. ActionRail v1 is planned for August 2026.\n\nSee [CONTRIBUTING.md](/ToolJet/ActionRail/blob/master/CONTRIBUTING.md) for development setup, safety-critical\nchange requirements, and pull-request guidance. Usage questions and bug-report\nrouting are documented in [SUPPORT.md](/ToolJet/ActionRail/blob/master/SUPPORT.md). Security reports must follow\n[SECURITY.md](/ToolJet/ActionRail/blob/master/SECURITY.md).\n\nRelease changes are tracked in [CHANGELOG.md](/ToolJet/ActionRail/blob/master/CHANGELOG.md). Participation in\nthe project is governed by the [Code of Conduct](/ToolJet/ActionRail/blob/master/CODE_OF_CONDUCT.md).\n\nActionRail is licensed under the [Apache License 2.0](/ToolJet/ActionRail/blob/master/LICENSE).\n\nCopyright 2026 ToolJet Solutions, Inc.", "url": "https://wpnews.pro/news/show-hn-actionrail-runtime-value-action-grounding-framework-for-ai-agents", "canonical_source": "https://github.com/ToolJet/ActionRail/", "published_at": "2026-07-25 20:48:25+00:00", "updated_at": "2026-07-25 21:22:51.111421+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "ai-safety", "developer-tools"], "entities": ["ToolJet", "ActionRail", "LangGraph", "Anthropic", "SQLite"], "alternates": {"html": "https://wpnews.pro/news/show-hn-actionrail-runtime-value-action-grounding-framework-for-ai-agents", "markdown": "https://wpnews.pro/news/show-hn-actionrail-runtime-value-action-grounding-framework-for-ai-agents.md", "text": "https://wpnews.pro/news/show-hn-actionrail-runtime-value-action-grounding-framework-for-ai-agents.txt", "jsonld": "https://wpnews.pro/news/show-hn-actionrail-runtime-value-action-grounding-framework-for-ai-agents.jsonld"}}