Skip to content

tangle-network/agent-app

Repository files navigation

@tangle-network/agent-app

npm npm provenance license

The application-shell layer for building agent products on the Tangle stack.

The substrate packages — @tangle-network/agent-runtime, agent-eval, agent-integrations, tcloud, sandbox — are the engine. This package is the shell: the chat tool-loop, the structured agent→app side channel, the integration-hub client, per-workspace billing, field crypto, and the web boundary utilities that every agent app otherwise rewrites by hand. You supply your domain through typed seams; the package supplies the mechanism and imports none of your code.

Who it's for: engineers building an agent product on the Tangle sandbox — a chat app, a copilot, an autonomous worker — who want the shell (chat routes, streaming, durability, approvals, billing, the tool side channel) as composable pieces instead of a per-app rewrite. It is not an agent framework or a model SDK: the reasoning lives in the sandbox agent; agent-app is everything around it — the turn plumbing, durability, and money.

Highlights

  • Structured tool side channelsubmit_proposal (approval-gated), schedule_followup, render_ui, add_citation, exposed as validated tool calls over three surfaces (HTTP route, per-turn MCP server, agent-runtime executor). No fenced-text parsing.
  • Bounded tool looprunAppToolLoop / streamAppToolLoop: stream a turn → collect tool calls → dispatch → fold results back → re-run, capped. These are 1:1 aliases of @tangle-network/agent-runtime's runToolLoop / streamToolLoop (the engine owns the loop; this package adds no logic on that path). Substrate-free behind a streamTurn seam, so it drives a sandboxed agent, a Worker, or an in-browser copilot unchanged.
  • Assembled chat verticalcreateChatTurnRoutes wires auth → thread/message store → streaming turn with buffered replay → uploads → sidecar question answering into one route factory, over authorize / produce / store / interactions seams. No hand-rolled orchestration. See examples/chat-app.md.
  • Sandbox-optional — the same tools, billing, eval, and loop work without a container. A fetch-only adapter maps any OpenAI-compatible stream (Tangle Router, tcloud) into the loop. See examples/browser-copilot.md.
  • Resumable turns (sandbox-free path) — for a browser/edge copilot streaming the Router directly, buffer a turn so a dropped tab loses nothing and a reconnecting client replays the tail. Sandbox products don't need this — the sandbox SDK already buffers + replays sessions (server-side reconnect via box.streamPrompt's lastEventId; browser-direct via box.mintScopedToken() + SessionGatewayClient). See examples/resumable-turns.md.
  • Composes the engine, never forks it/eval re-exports @tangle-network/agent-eval's verifier; /integrations wraps the hub; /tangle and /billing take the tcloud client as a structural contract. Engines are peer dependencies — you pin the version, nothing is bundled.
  • ESM, typed, zero runtime deps in the substrate-free modules (/runtime, /web, /crypto, /redact, /stream). Ships with .d.ts and npm provenance.

Install

pnpm add @tangle-network/agent-app

The engine packages you actually use are peer dependencies — install the ones your modules touch:

# /eval composes the eval engine; /integrations composes the hub client
pnpm add @tangle-network/agent-eval @tangle-network/agent-integrations
Peer Required by Range
@tangle-network/agent-eval /eval, /eval-campaign, /profile, /knowledge >=0.100.0
@tangle-network/agent-runtime /runtime, /chat-routes >=0.79.3
@tangle-network/agent-integrations /integrations >=0.44.0
@tangle-network/agent-interface /interactions, /chat-store, /harness >=0.15.0
@tangle-network/sandbox /sandbox, /profile, /skills >=0.9.7
@tangle-network/agent-knowledge /knowledge-loop >=1.7.0
@tangle-network/agent-profile-materialize /skills-placement >=0.6.0

All of these except agent-eval, agent-integrations, and agent-interface are declared optional peers, so a product that never imports the subpath installs nothing. driveSandboxTurn (/sandbox) calls box.driveTurn, which the SDK added in 0.10.5 — above the declared floor, so pin @tangle-network/sandbox >= 0.10.5 yourself if you use it.

Modules that import no engine package (/tools, /web, /crypto, /redact, /stream, /billing, /tangle — the last two take their client as a structural contract) need no peers.

Quick start

A product supplies its taxonomy (which proposal types exist, which are approval-gated) and its handlers (the real DB/vault writes), then wires the tool side channel to whichever surface it runs on.

import {
  buildAppToolOpenAITools,
  createAppToolRuntimeExecutor,
  type AppToolHandlers,
  type AppToolTaxonomy,
} from '@tangle-network/agent-app/tools'
import { runAppToolLoop } from '@tangle-network/agent-app/runtime'

// 1. Declare the domain (the package bakes in no proposal types or rules).
const taxonomy: AppToolTaxonomy = {
  proposalTypes: ['recommend', 'contact', 'other'],
  regulatedTypes: ['recommend', 'contact'], // these require a certified approver
}

// 2. Provide the side effects — your store, your validation.
const handlers: AppToolHandlers = {
  submitProposal,
  scheduleFollowup,
  renderUi,
  addCitation,
}

// 3. Advertise the tools to the model and route their execution.
const tools = buildAppToolOpenAITools(taxonomy)
const executeToolCall = createAppToolRuntimeExecutor({
  handlers,
  taxonomy,
  ctx: { userId, workspaceId, threadId },
})

// 4. Run a bounded, tool-driven turn loop over any backend.
const result = await runAppToolLoop({
  systemPrompt,
  userMessage,
  streamTurn,                                       // wrap your model / runAgentTaskStream
  executeToolCall,
  isExecutableTool: (name) => tools.some((t) => t.function.name === name),
})

console.log(result.finalText, result.toolResults)

streamTurn is the one seam that varies by backend. For an in-browser or edge copilot talking to an OpenAI-compatible endpoint, you don't write it by hand:

import { createOpenAICompatStreamTurn, resolveTangleModelConfig } from '@tangle-network/agent-app/runtime'

const cfg = resolveTangleModelConfig() // reads provider/model/key/baseUrl from env, or pass literals
const streamTurn = createOpenAICompatStreamTurn({ ...cfg, tools })

The full three-transport walkthrough (Tangle Router, tcloud, Vercel AI SDK) is in examples/browser-copilot.md.

Building the full server chat vertical instead — auth, thread/message tables, a streaming turn with buffered replay, uploads, and sidecar question answering — is the job of createChatTurnRoutes (/chat-routes) and the modules around it. The end-to-end assembly, including the durable plan/question workflow and the client composer, is in examples/chat-app.md.

How it's organised

One rule decides where anything lives:

Does the capability make sense without a specific app's tool side channel, approval queue, or chat route? Yes → it belongs in an engine package (contribute it down). No → it's app-shell, and it belongs here.

Everything here is reached through a typed seam — AppToolHandlers, AppToolTaxonomy, streamTurn, executeToolCall, verifyToken, KeyProvisioner / WorkspaceKeyStore / KeyCrypto. The package never imports product code and never hard-codes a domain value (a proposal type, a premium, a disclaimer); each is a parameter. New capability arrives as a new subpath, never a breaking change to an existing one.

Choosing a path

Three decisions cover most of the surface.

1. How does the turn run? Pick the transport by who's watching, not by feature.

Each primitive is written package → symbol; three packages ship similarly-named turn functions, and AGENTS.md has the full primitive table and the runLoop name-collision note.

Your turn Use Why
Interactive — a user is watching a chat or copilot sandbox → box.streamPrompt() held open for the turn, wrapped here as streamSandboxPrompt (/sandbox); for the browser leg, sandbox → box.mintScopedToken() + SessionGatewayClient (@tangle-network/sandbox/session-gateway) attaches the tab directly Worker lifetime ≈ turn length; a dropped tab replays the buffered tail on reconnect.
Autonomous — a mission step, queue job, cron, or inbound email, with nobody watching sandbox → box.driveTurn(), wrapped here as driveSandboxTurn (/sandbox), ticked from a durable driver; drop to raw box.dispatchPrompt({ detach: true }) + box.findCompletedTurn(turnId, { sessionId }) only when one pass is too coarse. runDetachedTurn (/chat-routes) bridges that detached run into the live buffer, so a browser opening the session mid-run still tails it token-by-token No consumer exists and Workers die in minutes; the platform runs the turn server-side and a crash re-dispatch is a lookup, not a second run.
Eval / CI — a long-lived harness process sandbox → box.streamPrompt() for a sandboxed harness; agent-runtime (root) → runToolLoop / streamToolLoop for an in-process model turn — runAppToolLoop / streamAppToolLoop (/runtime) are 1:1 aliases of those, not a second implementation The process outlives the run; durability adds nothing — a failed run is re-run, not resumed.

2. Assembled or à la carte? createChatTurnRoutes (/chat-routes) wires the whole server chat turn — auth, store, streaming, replay, uploads, interactions — over typed seams. Reach for the individual modules (/stream, /chat-store, /interactions) only to compose something the assembled route doesn't cover.

3. Sandbox or sandbox-free? The tools, billing, eval, and loop all work without a container: createOpenAICompatStreamTurn maps any OpenAI-compatible endpoint into the loop for a browser or edge copilot. Reach for /sandbox only when the turn needs a real container — bash, files, sub-agents, MCP.

Modules

Each subpath is an independent entry point — import only what you use; the root re-exports everything, but a subpath import keeps your bundle to what you touch.

The complete, always-current reference — every published subpath, its exported symbols, and its internal dependencies — is generated into docs/CODEMAP.md and kept honest by a CI check (regenerate with pnpm docs:gen). Start with the core entry points:

Run a turn

  • /tools — the structured agent→app side channel (proposals, follow-ups, citations, UI) as validated tool calls, over HTTP / MCP / runtime-executor surfaces.
  • /runtime — the bounded tool loop; the same loop drives a sandbox agent, a Worker, or an in-browser copilot behind one streamTurn seam.

The server chat vertical (examples/chat-app.md)

  • /chat-routescreateChatTurnRoutes: auth → store → streaming turn with buffered replay → uploads → sidecar question answering, assembled. Plus runDetachedTurn for autonomous turns a browser can still watch live.
  • /chat-store · /interactions · /durable-chat · /plans — persistence, human-in-the-loop asks, and the durable plan/question workflow around them.

On the sandbox

  • /sandbox — workspace provisioning + turn streaming.
  • /missions — durable multi-step orchestration: sequencing, budgets, approval gates, schedules.

React surfaces

  • /web-react — router-safe chat + observability components (never imports sandbox-only UI); /composer when the chat owns a full sandbox profile.

Utilities (zero-dependency)

See docs/CODEMAP.md for the rest — /billing, /tangle, /object-store, /trace, /theme, /eval, /app-auth, /platform, and more.

Missions: id shape and product columns

Two createMissionService seams adopters hit on day one:

  • generateId (on MissionServiceOptions) defaults to crypto.randomUUID() — a 36-char dashed UUID. If your mission table has an existing id shape (e.g. 32-hex to match D1 row defaults), inject your own generator; the service stamps it verbatim on the inserted record.
  • CreateMissionInput.extras carries opaque product-column values (a workflowId FK, a source-turn pointer) verbatim to MissionStorePort.insert(record, extras), so creation is a single write — no post-insert stamp. The service never reads them.

Compatibility

  • ESM only. Ships import + types conditions per subpath.
  • Runtimes: Node ≥ 20, Cloudflare Workers / edge, and the browser (the substrate-free modules use only Web-standard APIs — fetch, Web Crypto, TextEncoder).
  • TypeScript: strict; full .d.ts for every entry point.

Contributing

pnpm install
pnpm typecheck && pnpm test && pnpm build

Build is tsup (ESM + .d.ts), tests are vitest. A change keeps the suite green and follows the layering rule above — anything engine-general is contributed down to the substrate, not duplicated here. See AGENTS.md for the full contributor contract.

License

MIT

About

A feature-full starting point for production agent applications on Tangle.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages