npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@tangle-network/tcloud-agent

v0.3.3

Published

Agent run-loop primitive over Tangle sandbox transports — runs an AgentProfile against a brief with criterion gates, budget caps, and streaming events. Includes TangleToolProvider for Pi tool integration.

Readme

@tangle-network/tcloud-agent

Agent run-loop primitive for Tangle AI Cloud. Runs an AgentProfile against a brief, evaluates criterion gates after each iteration, respects budget caps, and streams events. Use it when you need a run-until-verified loop over the Tangle sandbox bridge — without rebuilding the iteration / verification / budget plumbing yourself.

npm version License: MIT/Apache-2.0

Why

Most agent libraries either (a) ship a giant framework with opinions baked in (LangChain, Claude Agent SDK), or (b) give you a streaming-chat primitive and leave the loop to you (Vercel AI SDK). tcloud-agent is the third thing: a single tight run-loop that knows about iterations, verification gates, and budget — and nothing else. You bring the profile, the brief, and the criteria; it handles the loop and the streaming.

It composes the Tangle TCloud SDK and runs over the sandbox-harness BridgeSession, so every iteration goes through Tangle routing with full control over operators, models, and policies.


Table of contents


Install

pnpm add @tangle-network/tcloud-agent @tangle-network/tcloud
# npm install / yarn add also work

Peer dependencies (only needed for the Pi-extension entry point — skip if you're driving the agent from a server / CLI):

pnpm add -D @mariozechner/pi-coding-agent @mariozechner/pi-tui

Requires Node 20+.


Quick start

import { TCloud } from '@tangle-network/tcloud'
import { agent } from '@tangle-network/tcloud-agent'

const client = new TCloud({ apiKey: process.env.TCLOUD_API_KEY! })

const result = await agent(client, {
  profile: 'sf-proposer',
  brief: 'Scaffold a Vite + React package and confirm pnpm build passes.',
  criteria: [
    { name: 'build-passes', check: async (ctx) => ({ ok: ctx.lastMessage.includes('pnpm build: ok') }) },
  ],
  budget: { iterations: 5, wallSec: 600, usd: 1 },
}).run()

console.log(result.verdict)        // 'verified' | 'blocked' | 'budget-exhausted' | 'error'
console.log(result.iterations)     // how many turns it took
console.log(result.usd)            // approximate spend (null if upstream didn't report usage)
console.log(result.transcript)     // full conversation

Five lines of config, one await. The loop runs until every criterion returns { ok: true }, the budget exhausts, or an error escapes the bridge.


Concepts

AgentRunOptions

| Field | Type | Description | |---|---|---| | profile | AgentProfile \| string | Inline profile object (from @tangle-network/sandbox) or a cataloged profile id resolved server-side | | brief | string | First user turn — what the agent is being asked to do | | criteria? | AgentRunCriterion[] | Completion gates evaluated after each iteration. Empty/undefined ⇒ first reply verifies. | | budget? | AgentBudget | { iterations?, wallSec?, usd? } — first breach exits with budget-exhausted | | resume? | string | Stable session id for the selected transport | | workspace? | { dir: string } | Transport-scoped workspace context; surfaced on AgentRunContext.workspaceDir. It is not injected into the prompt. | | unlock? | string | BridgeOptions.unlock. Falls back to process.env.BRIDGE_UNLOCK. | | bridgeUrl? | string | BYOB cli-bridge URL (forwarded to BridgeOptions.bridgeUrl) | | bridgeBearer? | string | BYOB cli-bridge bearer token | | transport? | AgentSessionTransport | Explicit runtime transport. Omit when using agent(client, opts) for router bridge. | | stream? | boolean | false to disable SSE — each iteration calls chat() non-streaming, emits one message.delta per turn. Default true. |

AgentRunCriterion

interface AgentRunCriterion {
  name: string  // stable id; surfaced on result.blockedBy if it fails
  check: (ctx: AgentRunContext) => Promise<{ ok: boolean; reason?: string }> | { ok: boolean; reason?: string }
}

Criteria are checked in order after each assistant turn. If any criterion returns ok: false, the loop continues to the next iteration with the criterion's reason appended as a follow-up user message. When all criteria pass, verdict is verified.

Empty criteria ⇒ the first assistant reply auto-verifies.

AgentBudget

interface AgentBudget {
  iterations?: number  // max loop iterations
  wallSec?: number     // wall-clock cap; first breach exits
  usd?: number         // best-effort spend cap (relies on ChatCompletion.usage)
}

Any field can be omitted. The first breach wins; verdict is budget-exhausted.

AgentRunResult and verdicts

interface AgentRunResult {
  verdict: 'verified' | 'blocked' | 'budget-exhausted' | 'error'
  iterations: number
  wallMs: number
  usd: number | null         // null when no usage was reported
  transcript: { role: 'user' | 'assistant' | 'system'; content: string }[]
  blockedBy?: string         // criterion.name when verdict === 'blocked'
  error?: string             // captured message when verdict === 'error'
}

run() never throws — bridge failures land as verdict: 'error' with the message captured. Branch on verdict, not on try/catch.

Streaming events

stream() emits AgentEvents in this order per iteration:

iteration.start
  → message.delta*           (zero or more)
  → iteration.complete
  → criterion.check*         (one per evaluated criterion)
... next iteration, or:
verdict                       (terminal — same payload as run() returns)

Examples

Example 1: Minimal run()

import { TCloud } from '@tangle-network/tcloud'
import { agent } from '@tangle-network/tcloud-agent'

const client = new TCloud({ apiKey: process.env.TCLOUD_API_KEY! })
const result = await agent(client, {
  profile: 'sf-proposer',
  brief: 'Reply with the literal text "ok".',
}).run()

console.log(result.verdict, '·', result.iterations, 'iter')

No criteria, no budget — first reply ⇒ verdict verified. Useful for smoke tests.

Example 2: Stream events to stdout

for await (const ev of agent(client, opts).stream()) {
  switch (ev.type) {
    case 'iteration.start':
      process.stdout.write(`\n[iter ${ev.iteration}] `)
      break
    case 'message.delta':
      process.stdout.write(ev.text)
      break
    case 'criterion.check':
      process.stdout.write(`\n  ${ev.ok ? '✓' : '✗'} ${ev.name}${ev.reason ? ` — ${ev.reason}` : ''}\n`)
      break
    case 'verdict':
      process.stdout.write(`\n[verdict: ${ev.verdict}, ${ev.iterations} iter, $${ev.usd ?? '?'}]\n`)
      break
  }
}

Live observability into the loop — exactly what run() consumes internally.

Example 3: Cataloged profile + custom criteria

const result = await agent(client, {
  profile: 'sf-proposer',
  brief: 'Scaffold an agent-runtime bundle for a music-producer agent.',
  criteria: [
    {
      name: 'manifest-valid',
      check: async (ctx) => {
        const m = ctx.lastMessage.match(/^\{[\s\S]+\}$/m)
        try {
          JSON.parse(m?.[0] ?? '')
          return { ok: true }
        } catch (err) {
          return { ok: false, reason: `manifest is not valid JSON: ${(err as Error).message}` }
        }
      },
    },
    {
      name: 'has-frontmatter',
      check: async (ctx) => ({
        ok: /^---\n[\s\S]+?\n---/m.test(ctx.lastMessage),
        reason: 'system-prompt.md frontmatter missing',
      }),
    },
  ],
}).run()

if (result.verdict === 'blocked') {
  console.error(`blocked by: ${result.blockedBy}`)
}

When a criterion fails, the loop appends Previous attempt failed: <reason> as the next user turn — the agent gets to fix it without you re-prompting.

Example 4: Budget-bounded loop

const result = await agent(client, {
  profile: 'sf-proposer',
  brief: 'Generate 50 manifest variants and report which pass schema validation.',
  budget: {
    iterations: 8,    // hard cap on loop count
    wallSec: 300,     // 5 minutes wall clock
    usd: 2,           // soft cap — relies on usage reporting
  },
}).run()

if (result.verdict === 'budget-exhausted') {
  console.warn(`stopped at ${result.iterations} iter / ${result.wallMs}ms / $${result.usd}`)
}

Caps are checked at iteration boundaries. First breach wins — useful for cron jobs and CI gates.

Example 5: Workspace-aware criteria

import { mkdtempSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'

const workspace = mkdtempSync(join(tmpdir(), 'agent-run-'))

const result = await agent(client, {
  profile: 'sf-proposer',
  brief: `Write the manifest into ${workspace}/manifest.json.`,
  workspace: { dir: workspace },
  criteria: [
    {
      name: 'file-written',
      check: async (ctx) => {
        const { existsSync } = await import('node:fs')
        return {
          ok: existsSync(`${ctx.workspaceDir}/manifest.json`),
          reason: 'manifest.json missing in workspace',
        }
      },
    },
  ],
}).run()

workspace.dir is surfaced to criteria and to transports that can enforce workspace context. It is not injected into the prompt; include the path in brief when a bridge-backed agent needs to know it.

Example 6: Inline AgentProfile

import type { AgentProfile } from '@tangle-network/sandbox'

const profile: AgentProfile = {
  name: 'one-off-reviewer',
  systemPrompt: 'You review TypeScript diffs and report PASS or FAIL with a one-line justification.',
  capabilities: { /* ... */ },
}

const result = await agent(client, {
  profile,                            // inline, not cataloged
  brief: 'Review this diff:\n```diff\n... \n```',
}).run()

Drop in any AgentProfile object directly — no server-side catalog lookup, no extra round-trip.

Example 7: Resume a session across calls

const first = await agent(client, {
  profile: 'sf-proposer',
  brief: 'Plan the family scaffold; reply with the plan only.',
}).run()

const second = await agent(client, {
  profile: 'sf-proposer',
  brief: 'Implement the plan you just produced.',
  resume: first.transcript[0]?.content?.startsWith('session:') ? /* extract id */ '' : undefined,
}).run()

resume forwards to BridgeOptions.resume — when the bridge supports it, the second agent starts on top of the first agent's session state.

Example 8: TangleToolProvider for Pi

import { TangleToolProvider, type CapabilityHandler } from '@tangle-network/tcloud-agent'

const handlers: CapabilityHandler[] = [
  {
    name: 'tangle_search',
    description: 'Search the Tangle docs',
    parameters: { query: { type: 'string' } },
    execute: async ({ query }) => ({
      results: await fetch(`https://docs.tangle.tools/search?q=${encodeURIComponent(query as string)}`).then(r => r.text()),
    }),
  },
]

const provider = new TangleToolProvider({ handlers })
const tools = provider.toOpenAITools()       // hand to your chat call
await provider.invoke('tangle_search', { query: 'sandbox bridge' })

Use directly with any OpenAI-compatible chat surface, or register the package as a Pi extension via the ./pi-extension subpath.


API reference

agent(client, options) / new Agent(client, options)

Build a runnable agent. Identical surface — function form is just new Agent(...).

| Method | Signature | Description | |---|---|---| | run | (): Promise<AgentRunResult> | Run to completion. Never throws — failures land on result.verdict === 'error'. | | stream | (): AsyncIterable<AgentEvent> | Event stream. Final event is always { type: 'verdict', ... }. |

run() is a thin consumer of stream() — the loop logic lives once.

Re-exports

Agent, agent, transport factories (routerBridgeTransport, localCliBridgeTransport, sandboxSdkTransport), all AgentRun* types, AgentSession* types, AgentEvent, TextPart, ToolPart, ToolState, TangleToolProvider, CapabilityHandler, ToolResult.

Subpath: @tangle-network/tcloud-agent/pi-extension

Drop-in Pi extension that registers the agent runner + TangleToolProvider into a Pi config. Peer-installable; requires @mariozechner/pi-coding-agent and @mariozechner/pi-tui.

import tcloudExtension from '@tangle-network/tcloud-agent/pi-extension'

export default {
  extensions: [tcloudExtension],
}

Architecture

                ┌─────────────────────────────┐
                │  Agent  /  agent()          │
                │  - iteration counter        │
                │  - criteria evaluation      │
                │  - budget tracker           │
                │  - event emitter            │
                └──┬───────────────────┬──────┘
                   │
                   ▼
        ┌─────────────────────────────┐
        │  AgentSessionTransport      │
        │  - router bridge            │
        │  - local cli-bridge         │
        │  - Sandbox SDK runtime      │
        └──────────────┬──────────────┘
                       │
                       ▼
        ┌────────────────────────────────────────┐
        │ Tangle Router / cli-bridge / Sandbox   │
        └────────────────────────────────────────┘

The runner owns the loop, not the runtime. The runtime is an AgentSessionTransport: router-mediated sandbox bridge for hosted/operator routing, direct cli-bridge for local shim usage, or the Sandbox SDK for first-class sandbox execution. Want operator rotation? Use TCloudClient.rotating() for stateless inference; sandbox sessions intentionally bind to one runtime.


Comparison vs alternatives

| | tcloud-agent | LangChain agent | Vercel AI SDK | Claude Agent SDK | |---|---|---|---|---| | Loop primitive | ✅ minimal | heavy framework | none (DIY loop) | subprocess runtime | | Streaming events | ✅ typed union | yes | yes | yes | | Criterion gates | ✅ first-class | manual | manual | manual | | Budget caps | ✅ first-class | manual | manual | manual | | Workspace policy | transport-scoped | manual | manual | implicit | | Verdict shape | typed enum | string | string | string | | Browser-runnable | partial (Node 20+ today) | yes | ✅ | ❌ subprocess | | Tangle routing / sandbox | ✅ native | adapter | adapter | n/a |

Use tcloud-agent when: you want a small, opinionated run-loop primitive over Tangle infrastructure with built-in criterion + budget gates. Skip it when: you only need a single chat call (use the TCloud SDK directly) or you want a full agent framework with planners / memory / tool registries (LangChain or Mastra).


FAQ

Q: How does this differ from @tangle-network/tcloud? A: TCloud is the SDK — single chat / completion / embedding calls, .bridge() for bridge sessions, .sandbox() / TCloudSandbox for Sandbox SDK access, and .rotating() for operator rotation. tcloud-agent adds the iteration / verification / budget loop on top of a sandbox-capable transport.

Q: What is "the bridge"? A: TCloudClient.bridge(options) opens a BridgeSession. On a normal TCloud client it routes through the Tangle Router. On TCloudClient.fromCliBridge(...) it talks directly to a local cli-bridge and maps resume to cli-bridge's session_id.

Q: Can I use this without the bridge / sandbox? A: Yes. Pass an explicit transport: sandboxSdkTransport({ sandbox }), localCliBridgeTransport(...), or your own AgentSessionTransport. Existing agent(client, opts) calls still use the router bridge adapter by default.

Q: Are tool calls supported? A: Not as first-class AgentEvents yet. Standard OpenAI-shaped output flows through message.delta until the upstream bridge/runtime protocol exposes tool parts as stable stream events.


Contributing

Code lives in tangle-network/tcloud under packages/tcloud-agent/.

git clone https://github.com/tangle-network/tcloud
cd tcloud
pnpm install
pnpm --filter @tangle-network/tcloud-agent build
pnpm --filter @tangle-network/tcloud-agent test

License

Dual-licensed under either:

at your option.