{"_id":"@ares-ai/forge","name":"@ares-ai/forge","dist-tags":{"alpha":"0.1.0-alpha.1","latest":"0.1.0-alpha.1"},"versions":{"0.1.0-alpha.1":{"name":"@ares-ai/forge","version":"0.1.0-alpha.1","description":"Ares friendly authoring facade: agent, tool, run, on, before, visible, session","license":"Apache-2.0","type":"module","sideEffects":false,"engines":{"node":">=22"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./stores":{"types":"./dist/stores.d.ts","import":"./dist/stores.js"},"./canonical":{"types":"./dist/canonical.d.ts","import":"./dist/canonical.js"},"./approvals":{"types":"./dist/approvals.d.ts","import":"./dist/approvals.js"},"./postgres":{"types":"./dist/postgres.d.ts","import":"./dist/postgres.js"},"./a2a":{"types":"./dist/a2a.d.ts","import":"./dist/a2a.js"},"./observability":{"types":"./dist/observability.d.ts","import":"./dist/observability.js"},"./testing":{"types":"./dist/testing.d.ts","import":"./dist/testing.js"},"./providers":{"types":"./dist/providers.d.ts","import":"./dist/providers.js"},"./eval":{"types":"./dist/eval.d.ts","import":"./dist/eval.js"},"./mcp":{"types":"./dist/mcp.d.ts","import":"./dist/mcp.js"}},"dependencies":{"@ares-ai/runtime":"0.1.0-alpha.1"},"publishConfig":{"access":"public","provenance":true},"ares":{"stability":"alpha-supported"},"gitHead":"ad5f3759154e8ee0dd31e1c1147bde854ec2036f","_id":"@ares-ai/forge@0.1.0-alpha.1","_nodeVersion":"26.5.0","_npmVersion":"11.17.0","dist":{"integrity":"sha512-M4ceaY2Z+XyO2qzDaoIMG1hwvKbRkv5+WszQZpUAWvaxe1oNTpHb8uRkla+092/kBGib6RaZjY9W70eaKou0cA==","shasum":"8ba828afc66cef6889544ca72f915598129ec8c3","tarball":"https://registry.npmjs.org/@ares-ai/forge/-/forge-0.1.0-alpha.1.tgz","fileCount":37,"unpackedSize":17642836,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIFODHEEoXyTZNrKO8JUmH+TepBnO5807anv73VMrVDm0AiBMGsIHS344yYbJoWEnJkEv1C5K6Vu2p7/hICyzfIgUaw=="}]},"_npmUser":{"name":"ashishthomas","email":"ashishthomas2202@gmail.com"},"directories":{},"maintainers":[{"name":"ashishthomas","email":"ashishthomas2202@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/forge_0.1.0-alpha.1_1784773322641_0.05584494204293233"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-23T02:22:02.388Z","0.1.0-alpha.1":"2026-07-23T02:22:02.860Z","modified":"2026-07-23T02:22:03.158Z"},"maintainers":[{"name":"ashishthomas","email":"ashishthomas2202@gmail.com"}],"description":"Ares friendly authoring facade: agent, tool, run, on, before, visible, session","license":"Apache-2.0","readme":"# Ares Forge (`@ares-ai/forge`)\n\nThe friendly authoring facade for the Ares framework. One import, one small\nsurface, compiled over the governed runtime.\n\nStatus: **working facade, alpha.** The friendly API drives real models end to end\nthrough gateway, OpenAI, Anthropic, Google, and **Amazon Bedrock** (BYOK)\nadapters, including the full multi-turn **tool loop with tool-call ids** and SSE\ntoken streaming. Bedrock uses a built-in **AWS SigV4** signer over the unified\nConverse API. A credential-free `MockModel` is the local/test default.\n\n> **Provider evidence:** adapters are implemented, and the gateway/Anthropic/Google\n> request shapes are gated offline in CI; **no adapter has live-credential\n> certification** (a durable report naming provider, model, date, test version and\n> result), and none is production-supported in the alpha. `openaiModel()` and\n> `bedrockModel()` currently have **no automated coverage**. The authoritative,\n> per-capability status is the\n> **[provider capability matrix](../../../docs/v0.1/providers.html)** — it wins over\n> any claim elsewhere.\n\nImplemented and\nverified: the public surface, the eight-event model, private-by-default context,\nthe bounded multi-turn tool loop, **tool contracts** (`requires` + input/output\nvalidators), **strict `returns`**, a streaming awaitable **`Run`**\n(`on`/`cancel`/`stream`, `AbortSignal` threaded to providers and tools), **effect\nreceipts + uncertain-outcome reconciliation**, **opt-in authorization**, and\n**executable knowledge/skills/memory**. A parallel **Python facade** (`from ares\nimport agent, tool, run, workflow, …`) is verified too. The **default provider is\nthe Ares gateway** (Anthropic Messages-compatible `/v1/messages`), which brokers\nOpenAI/Anthropic/Bedrock with no key pasted.\n\n**Effects do real work.** Every tool declares an `effect` — `pure` · `read` ·\n`write` · `irreversible` (no `financial-write` — sensitivity is _policy_, not an\neffect). The label drives real behavior automatically: `pure`/`read` run **in\nparallel**, `pure` results are **memoized**, `write`/`irreversible` **serialize**\nand are **logged in `result.effects`** (audit); transient failures **auto-retry**\nfor safe effects but `irreversible` **never** does; and `run(input, { dryRun: true })`\n**previews** — consequential tools are recorded, not executed.\n\n**Authorization is opt-in, not default friction.** Declaring a tool and giving it\nto the agent **is** the authorization — a declared `write` runs. Approval is a\npolicy you add when you want it: `.before(tool, …)` for a specific tool,\n`approvals: \"review\"` to gate every consequential tool, or `approvals: \"wait\"` to\npause the run and resume on `run.approve()`/`run.deny()`. The only thing blocked by\ndefault is an **undeclared** shorthand tool (its effect was only presumed).\n\n**Safety hardening:** per-user/per-org memory **fails closed** without an id (no\ncross-tenant leakage); the `remember` write is classified as a governed write with\n**secret filtering** and a **pluggable `MemoryStore`**; consequential tools are\n**serialized** while read/pure run in parallel; `agent.asTool()` **inherits the\nsub-agent's max effect** (a refund-capable sub-agent is consequential); output is\n**validated before it streams** when a `returns` schema or output gate is set;\napprovals **register before the event fires** (no approve/deny race); unresolved\nskill references **warn** instead of silently doing nothing.\n\n**Capabilities that execute (not just warn):** `knowledge` sources are **searched**\nvia an auto-added `search_knowledge` tool; inline `skill`s are **loaded on demand**\nvia `load_skill` (progressive disclosure, `load: \"eager\"` for always-on); `memory`\nadds a `remember` tool and **retrieves** relevant notes into context at run start,\nscoped `per-user`/`per-org` and persisting across runs (in-memory default, durable\nstore pluggable).\n\n**Rich responses, not just strings.** A tool can **return a widget** (`card`,\n`table`, `chart`, `list`, `code`, `image`, `progress`, `approval`, `form`, or a\ncustom one via `widget()`), and the run records it in ordered **`result.parts`**\n(text · widget · tool · file) so a UI can render a structured reply. Every widget\ncarries a stable **`id`**, a **`state`** (`streaming`/`ready`/`error`), and a\n**`revision`**, so a tool can **stream one widget in place**: call `ctx.emit(widget)`\nwith a rising `revision` (use `update()` to bump it), and `run.stream()` yields each\nrevision live. **`defineWidget({ name, props })`** builds a **schema-validated**\nfactory — malformed props throw before the frontend, each instance gets a **unique\nid**, and its **`.update()` re-validates** merged props; a run-level **`WidgetRegistry`**\nenforces monotonic revisions and terminal-state rules while streaming. Widget\n**actions bind to a capability** (pass the `Tool`, not a string), and the browser\nvalue is **untrusted**: the host calls **`agent.invokeAction({ action, args, widgetId,\nrevision, idempotencyKey, tenant, user, runId })`** — one **atomic** call that\n**synchronously reserves** the key (two concurrent submissions can't both execute),\nrejects a **stale revision**, **replays** a completed key without re-executing,\nbinds identity via a **canonical SHA-256 digest** (`deriveActionKey` — length-framed\nfields + sorted-key args, byte-identical in TS and Python, proven by golden vectors),\nauthorizes (ownership + args + gates + approval mode), runs the tool, and records an\n**effect receipt** (`authorizeAction(...)` is the check-only half). `reserve()` is\n**one atomic step** that claims the key _and_ checks staleness together (no TOCTOU),\nholds a **lease** so a crash mid-flight can be reclaimed, and — with the durable\n**`fileActionStore`** (atomic tmp+rename writes; `ares/stores`) — persists completed\noutcomes, acted revisions, and in-flight leases across a restart. The `ActionStore`\ninterface is **async** so a transactional backend (PostgreSQL) can implement it as a\nsingle DB transaction. A dependency-free, SSR-safe\n**`renderWidget()`** turns any widget into HTML (with `widgets.css`; emits\n`data-widget-id`/`-state`/`-revision`); React/Vue/Svelte wrappers are thin adapters\nover it. Verified end to end in `examples/widgets.mjs` (39) and Python's\n`examples/parity.py` (39).\n\n**Lowers into the canonical runtime.** `import { lower } from \"@ares-ai/forge/canonical\"` and\n`lower(agent)` translates a friendly `agent()` into a canonical **`AgentDefinition`**,\ncompiles it to a validated **`AresAgentIR`** + **`AresExecutionPlan`** through the\n_same_ `compileAgent` the rest of the platform uses, and checks every synthesized\ncapability contract and the IR against the canonical schemas (`capability`,\n`agent-ir`, `execution-plan`). Each `tool()` becomes a governed capability — its\neffect maps to the canonical class/risk (`write → reversible_write / r3_reversible`,\n`irreversible → irreversible_write / r4_consequential`, reconcile → `reconciliation:\nsupported`). This is the front-end emitting real IR for the governed back-end;\nverified in `examples/lowering.mjs`. **Python is at parity** — `from ares.lower import\nlower` compiles through the same canonical `compile_agent` (`examples/lowering.py`).\n\n**Executes through the canonical engine — a first-class `run()` path.**\n`agent.run(input, { runtime: \"canonical\" })` runs a friendly `agent()` **through the real\n`AgentLoopExecutor`** (lazy-loaded, so the base `import { agent }` stays light). Each\n`tool()` is dispatched as a **governed `AgentTool`**, the model turn flows through the\ncanonical **provider runtime**, and the engine records **budget, usage, and checkpoints**.\nCanonical events map into `run().on(...)` / `run().stream()`. It **executes the exact lowered\nIR** (`lower(agent, {tools}).ir`) and enforces the SAME governance as the local loop —\npolicies, `.before()` act/tool/**output** gates, access filtering, approvals, and\noutput-validation-before-emit — with **identical governance decisions and governance events**\nacross runtimes (both derive from one `#decide` evaluator). It supports **durable auto-persist +\n`agent.resume(runId, { durable })`** across a crash (fail-closed on corrupt state; every fresh/resumed durable\nrun holds a heartbeat-renewed fenced lease). `effects: fileEffectStore(dir)` adds non-expiring effect intents,\nimmutable receipt replay, stable external idempotency keys, and fail-closed uncertainty. Verified in\n`examples/canonical-run.mjs`, `canonical-default.mjs`, `governance-parity.mjs` (33/33), and\n`durability.mjs` (38/38).\n\nFor a production, cross-host deployment, use the unified PostgreSQL store. The same object\nbacks run snapshots/ownership, Effect intents/receipts, and manifest-bound UI actions:\n\n```ts\nimport { agent } from \"@ares-ai/forge\";\nimport { postgresStore } from \"@ares-ai/forge/postgres\";\n\nconst postgres = await postgresStore({\n  database: { connectionString: process.env.DATABASE_URL },\n});\n\nconst result = await support.run(\"refund order A-1\", {\n  runtime: \"canonical\",\n  durable: postgres,\n  effects: postgres,\n});\n\n// For widget actions: agent({ ..., actionStore: postgres })\nawait postgres.close(); // after active runs drain\n```\n\n`postgresStore()` applies a checksummed additive migration by default; set\n`schemaManagement: \"external\"` when migrations are owned by deployment tooling. PostgreSQL\nserver time, row locks, and monotonic fencing reject stale hosts. Effect intent and receipt\nmutations validate that same active lease. The compatibility constructors\n`postgresSnapshotStore`, `postgresEffectStore`, and `postgresActionStore` return this unified\nstore as well.\n\nManaged approvals use the canonical approval-command engine rather than trusting a tool name:\n\n```ts\nimport { approvalActor } from \"@ares-ai/forge/approvals\";\n\nconst reviews = postgres.approvalWorkflow(); // approvalWorkflow() is the in-memory dev option\nconst payments = agent({\n  name: \"Payments\",\n  instructions: \"Process approved payments.\",\n  tools: [sendWire],\n  runtime: \"canonical\",\n  approvals: {\n    mode: \"wait\",\n    workflow: reviews,\n    requiredApprovals: 2,\n    eligibleApproverRoles: [\"finance.approver\", \"security.approver\"],\n    requiredRoleCoverage: [\"finance.approver\", \"security.approver\"],\n  },\n});\n\nconst pending = payments.run(\"send it\", {\n  durable: postgres,\n  effects: postgres,\n  context: { user: { id: \"requester-42\" } },\n});\nlet requestId = \"\";\npending.on(\"approval\", (event) => {\n  requestId = event.requestId ?? \"\";\n});\nawait pending;\n\nawait reviews.endorse(\n  requestId,\n  approvalActor({\n    principalId: \"reviewer-1\",\n    roles: [\"finance.approver\"],\n  }),\n);\n// A second eligible actor supplies the other required role, then:\nawait payments.resume(pending.id, { durable: postgres, effects: postgres });\n```\n\nReviewer identity must come from trusted server authentication. Requests bind the run,\ncapability, and argument digest; wrong actors, expiry, duplicate endorsements, stale request\nIDs, and insufficient quorum fail closed. Grants use deterministic Effect consumption, so a\ncrash/replay does not spend or execute twice.\n\n**Built for speed and long tasks:** independent tool calls run **in parallel**;\nruns default to **32 turns** with `limits: { turns, toolCalls, timeMs,\nparallelReads, inputTokens, outputTokens, historyFraction }` budgets. Gateway prompt\ncaching is on by default; canonical history is deterministically bounded; and unchanged\nagents reuse precompiled IR. Run `pnpm bench:facade` for the reproducible cold/warm compile,\nturn-overhead, cache-economics, and resume-latency gate.\n`agent.asTool()` composes an **isolated sub-agent**; `workflow(($, input) => …)`\nruns deterministic multi-step procedures. The workflow runtime now adds typed steps,\npersisted branch choices, bounded `repeat`/`while`, durable subplans, waits/resume,\nparallel checkpoints, and reverse-order saga compensation. Consequential workflow\ntools persist intent before dispatch and receive a stable `idempotencyKey`; unresolved\nnon-idempotent intents fail closed. Use `MemoryWorkflowStore` for development or\n`postgres.workflows` for cross-process PostgreSQL restart.\n\n```ts\nconst refund = workflow(\n  async ($, ticket: Ticket) => {\n    const route = await $.branch(\n      \"route\",\n      () => ticket.amount > 100,\n      () => \"manual\",\n      () => \"automatic\",\n    );\n    const prepared = await $.compensate(\n      \"reserve-refund\",\n      () => $.tool(reserveRefund, ticket, { id: \"reserve\" }),\n      (reservation) => releaseRefund(reservation),\n    );\n    const approval =\n      route === \"manual\"\n        ? await $.wait(\"finance\", \"approval\", { amount: ticket.amount })\n        : \"automatic\";\n    return { prepared, approval };\n  },\n  { name: \"refund\" },\n);\n\nawait refund.run(ticket, { runId: \"refund-42\", store: postgres.workflows });\nawait refund.resume(\"refund-42\", ticket, {\n  store: postgres.workflows,\n  signals: { finance: \"approved\" },\n});\n```\n\n**Multi-agent work uses the same governed boundaries.** `handoff(child, { context:\n[\"tenant\"] })` creates a capability whose effect is inherited from the child and whose\ncontext is attenuated by default. Durable handoffs use a deterministic child run ID and\nthe parent's snapshot/effect stores; a child approval suspends the parent, and parent\nresume continues the child without replaying completed work. `team()` provides\n`round-robin`, `manager`, and `debate` strategies with per-member run/effect evidence.\nRemote A2A agents join through `a2aAgent()` from `ares/a2a`; it requires a compiled,\ngoverned binding and reuses the same handoff path.\n\n```ts\nimport { agent, handoff, team } from \"@ares-ai/forge\";\n\nconst lead = agent({\n  name: \"Lead\",\n  instructions: \"Delegate specialist work.\",\n  handoff: [handoff(researcher, { context: [\"tenant\"] })],\n});\n\nconst reviewTeam = team({\n  name: \"Review\",\n  members: [security, reliability],\n  manager: lead,\n  strategy: \"debate\",\n});\n\nconst reviewed = await reviewTeam.run(\"Review this release\", {\n  runId: \"run_01J00000000000000000000042\",\n  durable: postgres,\n  effects: postgres,\n});\n```\n\n**Observability and evals are first-class.** Pass a `TraceCollector`, `fileTelemetry()`,\nor `openTelemetry(tracer)` to an Agent/run. Ares emits run/model/tool/effect spans with\nlatency, token/cache usage, cost, effect class, and failure status on local and canonical\npaths. Inspect JSONL evidence with `ares inspect --trace .ares/trace.jsonl --run <id>\n--html .ares/timeline.html`. `evalset()` adds exact, rubric, and strict model-judge\nscorers; consequential evals require an explicit isolated-environment opt-in and retain\ntheir receipts.\n\n```ts\nimport { evalset, exact, rubric } from \"@ares-ai/forge\";\nimport { fileTelemetry } from \"@ares-ai/forge/observability\";\n\nconst observed = agent({\n  ...spec,\n  telemetry: fileTelemetry(\".ares/trace.jsonl\"),\n});\nconst quality = evalset({\n  name: \"support\",\n  cases: [{ name: \"refund policy\", input: \"Can I refund?\", expected: \"Yes\" }],\n  scorers: [\n    exact(),\n    rubric(\"concise\", ({ result }) => (result.text.length < 200 ? 1 : 0)),\n  ],\n});\nconst report = await quality.run(observed);\nif (!report.passed) throw new Error(\"evaluation gate failed\");\n```\n\n**Not yet done (honest):** `.run()` defaults to the light local loop; the canonical durable\nengine is opt-in via `runtime: \"canonical\"` (it stays out of the base import by design).\nDurable persistence is certified file-backed/single-host and PostgreSQL 18/cross-host.\nCanonical execution + MCP are **TypeScript-only** (Python has lowering, no live\nengine). Still remaining: **framework-specific** widget renderers (React/Vue/Svelte — the\nprotocol, built-ins, and a vanilla HTML renderer ship today); **live-credential certification\nfor every provider adapter** (see the [matrix](../../../docs/v0.1/providers.html)); and\npublished npm/pip packages. The **Python\nfacade is at parity** for the facade-level bits — effect receipts, uncertain-outcome\nreconciliation, durable workflows, access groups, and durable file-backed memory/session\nstores — verified by `examples/parity.py`.\n\n## Example\n\n```ts\nimport { agent, tool, visible } from \"@ares-ai/forge\";\n\nconst issueRefund = tool({\n  name: \"Issue refund\",\n  effect: \"write\",\n  run: async ({ amount }) => stripeRefund(amount),\n});\n\nconst support = agent({\n  name: \"Support\",\n  instructions: \"Resolve customer support cases.\",\n  tools: [issueRefund],\n  memory: \"per-user\",\n  model: \"anthropic/claude-sonnet-4-5\", // one string; keys from env. Also\n  // \"openai/gpt-4o\", \"google/gemini-2.5-pro\", \"bedrock/…\", \"groq/…\",\n  // \"cerebras/…\", \"mock\" (offline), or a bare id for the default gateway.\n  // Any object implementing { generate } plugs in a custom provider.\n});\n\n// gate consequential steps\nsupport.before(issueRefund, (e) =>\n  e.args.amount > 100 ? e.deny(\"needs approval\") : e.allow(),\n);\n\n// observe the eight canonical events\nsupport.on(\"output\", (e) => render(e.content));\n\nconst result = await support.run(\"Where is my order?\", {\n  context: {\n    user: { name: visible(\"Ada\"), email: \"ada@example.com\" }, // name visible, email private\n  },\n});\n```\n\n## Surface\n\n- `agent({ name, instructions, tools?, memory?, model? })` — `name` and\n  `instructions` required. `model` is a `\"provider/model-id\"` string\n  (env-credentialed; `resolveModel()` is the underlying helper) or any object\n  implementing `{ generate }`.\n- `.run(input, { context?, session?, on?, model?, includeMemory? })` → uniform\n  `Result` (`text`, `output`, `usage`, `messages`, optional `memory`).\n- `.on(event, fn)` — observe; never breaks a run.\n- `.before(step, fn)` — gate a tool, `\"act\"` (any consequential tool), or\n  `\"output\"`; throwing fails closed.\n- `tool(def)` / `tool(description, run)` — the shorthand is fail-safe\n  consequential + approval-required.\n- `visible(value)` — expose a context field/item to the model (private by\n  default).\n- `session(id)` — a conversation/job thread.\n\n## Events\n\n`start · working · tool · approval · output · finish · error · cancel` — the only\nbuilt-in event strings. Everything else is a typed sub-event.\n\n## Develop\n\n```\nnode ../../../node_modules/typescript/lib/tsc.js -p tsconfig.json   # typecheck/build\nnode examples/demo.mjs                                              # run the demo (after build)\npnpm test                                                          # vitest (Node >= 20.6)\n```\n","readmeFilename":"README.md","_rev":"1-b082b1e0070fa98a181ff46120e9ad9b"}