Skip to content

Agent runtime, sessions, context, limits ​

Page declarations describe a surface. The agent runtime decides how a conversation uses that surface: which model it calls, how much history it keeps, when an action needs approval, and what evidence remains after a run.

This page separates terms that are often used interchangeably. A configuration mistake in one layer should not be diagnosed as a problem in another.

The objects and what they count ​

ObjectWhat it representsImportant boundary
AgentRuntimeOne wired conversation environment: model client, policy, hooks, telemetry, sessionConfiguration is resolved once per mounted runtime
AgentSessionThe dialogue goal, history, event stream, pending approvals, and pending handoffssend() is serialized; a second send waits for the first
TurnOne model response and its tool calls inside a single send()Bounded by limits.maxTurns
Model callOne request to the model provider, including retriesCounted by telemetry; may exceed the turn count
Context used for compactionBase system prompt, compressed summary, and recent messagesBounded by limits.contextTokens, not by the response length

maxTurns is not a request limit. If the model performs five tool rounds and then answers, that is six turns. If a provider retries once after a network failure, the turn count does not change, but the model-call count does.

maxTokens is also a different setting. It is the provider's output limit for one response. contextTokens is the history budget used to decide when to compact. The per-turn page snapshot, tool schemas, and rebuilt system prompt are added after that decision, so the final provider request can exceed the nominal history budget.

Configuration and precedence ​

The runtime resolves configuration in this order:

  1. framework defaults;
  2. environment values such as VITE_AGENT_BASE_URL and VITE_AGENT_MODEL;
  3. explicit values passed through createPling({ agent: { config } }), a surface's config prop, or an injected model.

Explicit values win per field. Nested limits and retry values are merged instead of replacing the entire object.

FieldDefaultMeaning
baseUrlemptyOpenAI-compatible endpoint; a relative path uses the same-origin proxy
modelemptyProvider model identifier
apiKeyemptyBearer token for a direct request
temperature0.3Generation randomness
maxTokens1024Output limit for one response
systemPromptemptyApplication role appended to the framework rules
streamtrueConsume server-sent text deltas when supported
defaultApproval"danger"Default action policy when an action omits confirm
minActionIntervalMs0Minimum delay between actions in the same batch
retry.maxRetries3Retries after the first failed request; range 0 to 5
retry.baseDelayMs500Initial exponential-backoff delay
retry.maxDelayMs4000Delay ceiling
limits.maxTurns10Model turns allowed inside one send()
limits.contextTokens8192History budget used for compaction; page context and tool schemas are additional
limits.reserveForOutput1024Context reserved for the next response
limits.compactRatio0.8Fraction of usable context that triggers compaction
limits.keepRecent6Recent messages kept verbatim during compaction

An injected llm is sufficient for a deterministic test or a custom gateway client. In that case baseUrl and apiKey are not required for readiness. The runtime still checks limits and retry values.

What compaction does ​

The history budget is not the same as the provider's full context window. Pling reserves space for the next response and compacts early:

text
usable = contextTokens - reserveForOutput
compactAt = floor(usable × compactRatio)

With the defaults, 8192 - 1024 = 7168 tokens are usable and compaction starts at about 5734 tokens.

When the estimated history exceeds that line, the runtime summarizes older messages and keeps the latest keepRecent messages. It must not split an assistant tool-call message from its tool result; doing so produces an invalid message sequence. The system prompt is rebuilt for each request, so the model always sees the current page instead of a cached page snapshot.

Compaction preserves the task goal, decisions, constraints, and unfinished work. It is not a transcript archive. If the application needs an auditable conversation log, record session events outside the context window.

Wiring the runtime ​

The same agent options reach the shared runtime in every adapter. Only the mount code differs.

ts
import { createPling } from "@plingjs/vue";

const pling = createPling({
  agent: {
    config: {
      limits: { maxTurns: 100, contextTokens: 16_384, keepRecent: 8 },
      retry: { maxRetries: 2 },
    },
    onEvent(event) {
      if (event.type === "error") console.warn(event.code, event.message);
    },
  },
});
tsx
import { createPling, PlingProvider, AgentDock } from "@plingjs/react";

const pling = createPling({
  agent: {
    config: { limits: { maxTurns: 100, contextTokens: 16_384, keepRecent: 8 } },
  },
});

export function App() {
  return (
    <PlingProvider pling={pling}>
      <TodoPage />
      <AgentDock />
    </PlingProvider>
  );
}
ts
import { createPling } from "@plingjs/vanilla";

const pling = createPling({
  agent: {
    config: { limits: { maxTurns: 100, contextTokens: 16_384, keepRecent: 8 } },
    onEvent(event) {
      if (event.type === "error") console.warn(event.code, event.message);
    },
  },
});

pling.dock({ title: "Order assistant" });
ts
import Vue from "vue";
import { createPling } from "@plingjs/vue2";

const pling = createPling({
  agent: {
    config: { limits: { maxTurns: 100, contextTokens: 16_384, keepRecent: 8 } },
  },
});

Vue.use(pling.plugin);
pling.dock({ title: "Order assistant" });

maxTurns: 100 is appropriate for a long tutorial or demo. Production defaults to 10. Raising the limit increases the possible cost of one user request and should be paired with a per-run or per-tenant budget.

Tool lifecycle hooks ​

Tool hooks are an application-level policy seam, not another declaration mechanism. PreToolUse runs after the model has chosen an action but before the executor reaches the business control. It may block the call. A blocked call returns HOOK_BLOCKED, emits a blocked event, and does not run the business handler. PostToolUse runs after execution and may return a replacement result; later hooks and the final tool message see the replacement.

HookRunsMay changeStill cannot
PreToolUseAfter action selection, before executionBlock with a reason; inspect the action id, model tool name, arguments, and turnCreate an undeclared action, approve itself, or bypass the executor
PostToolUseAfter executionReplace the structured result returned to the model and event streamUndo a business side effect or turn a failed result into an executed action
ts
import { HookRegistry } from "@plingjs/agent";
import { createPling } from "@plingjs/vue";

const hooks = new HookRegistry();

const removePre = hooks.addPreToolUse(({ id }) => {
  if (id.startsWith("delete")) {
    return { blocked: true, reason: "Deletion requires a separate approval policy" };
  }
});

const removePost = hooks.addPostToolUse(({ result }) => {
  if ("error" in result) return;
  return { result: { ...(result.result as Record<string, unknown>), email: "[redacted]" } };
});

const pling = createPling({
  agent: { config: { limits: { maxTurns: 100 } }, hooks },
});

// Call these when the owning plugin or policy module unloads.
removePre();
removePost();

Hooks run in registration order. A pre-hook stops the chain on the first block; a post-hook's non-undefined return becomes the input to the next hook. Keep both handlers deterministic and bounded. Do not use a post-hook as the only redaction boundary for page observations: sensitive() governs declared outbound values, while hooks operate on action results. Do not log raw arguments or results by default; they may contain business or personal data.

Because hooks sit in the runtime rather than in the page adapter, all four adapters share the same behaviour. They are suitable for tenant policy, a kill switch for one capability, result masking, metrics, or a review queue. They are not a durable audit log and they do not decide backend authorization.

Session lifecycle ​

AgentSession exposes:

Method or propertyBehavior
send(input)Adds the user message and runs the loop; concurrent calls are serialized
cancel()Aborts the in-flight model request and stops at the next safe point
reset()Clears messages, summary, continuity state, and current-run counters; approval and handoff queues must be cleared separately
status()Reports idle, running, or cancelled; approval and handoff waits are exposed through their queues and events
events()Returns the UI event stream used by the chat and activity views; the session itself does not enforce a length limit
continuityReports whether a cross-document checkpoint is active

Clearing the visible transcript without calling reset() is not a new conversation. The old history may still be sent on the next request. The built-in clear control calls session.reset() and also clears the approval and handoff queues and resets telemetry. A host that calls reset() directly must perform those queue and telemetry steps itself.

Observability ​

Three records answer different questions:

  • session events describe what the user and interface saw: messages, actions, approvals, handoffs, errors;
  • telemetry summarizes model calls, tool calls, tokens, estimated cost, duration, retries, and error rate;
  • a trace stores the exact recent model requests, including messages and tool schemas, for debugging.

Traces are opt-in and bounded. They are diagnostic data, not an audit log, and they may contain page text or prompt content. Do not retain them by default in production.

AdapterRuntime diagnostics
Vue 3<AgentDock :debug="{ maxCalls: 8 }"> includes the request trace in the activity view
ReactAgentDock.onSessionEvent, createPling({ agent: { onEvent } }), and injected telemetry; no built-in inspector
Vanilla / jQueryonSessionEvent, then render the events in the host application
Vue 2.7Shared vanilla dock events; no built-in inspector

Failure modes ​

SymptomLikely causeFix
NOT_CONFIGURED before any requestbaseUrl, model, or a direct endpoint's apiKey is missingFill .env.local or pass an explicit config
A run stops after a few turns without a final answermaxTurns was reached; this ends with a notice rather than a structured errorInspect the session events, then raise the limit deliberately
The answer forgets an earlier decisionCompaction replaced old messages with a summaryReduce tool output, expose a smaller state view, or keep the relevant decision in application state
A long run becomes unexpectedly expensivemaxTurns or maxTokens is high, or retries are multiplying callsSet budgets and inspect telemetry by request
Context never compactsEstimated tokens stay below the threshold or the input is still smallDo not force compaction; inspect the actual context size
A reset does not hide old historyThe UI was cleared without session.reset()Reset the session and the queues together
A provider sends no streaming textstream is enabled but the gateway does not support SSEDisable streaming or fix the gateway

Evidence ​

  • Runtime and limit defaults: packages/agent/src/config.ts.
  • Context compaction: packages/agent/src/history.ts.
  • Session behavior: packages/agent/src/session.ts.
  • Bounded debug trace in Vue 3: examples/recipes/vue3.
  • Model settings: packages/vue/src/model-settings.ts.

Version and compatibility ​

ItemValue
Applies to@plingjs/agent 0.1.0-alpha.2
Introduced0.1.0-alpha.2 (first published alpha)
RuntimeBrowsers with ES2022 modules; Node.js 20 or later for Node-side use
Peer dependenciesNone
StabilityPre-1.0 alpha; a minor release may change the API

The limits and defaults on this page come from packages/agent/src/config.ts at this version. Session, context, compaction, and limit fields are pre-1.0; after a minor upgrade, re-read this page instead of assuming the earlier defaults still apply.