instrumentedCodingAgent
Coding-agent runtime whose executeStep(...) calls inherit a @pleach/observe sub-agent attribution scope. Every recordCall(...) downstream of the step carries [subagent, ...] without threading a recorder argument.
instrumentedCodingAgent wraps createCodingAgentRuntime from
@pleach/coding-agent with a subagent(name).run(...) scope
around each executeStep(...) call. The decomposed sub-roles
inside a coding turn — planner, executor, critic, and the
sandbox-backed tool calls in between — all sit inside that
AsyncLocalStorage scope, so per-call cost rows on the
observability dashboard come back tagged by the sub-agent that
issued the LLM call.
Best fit: a coding-agent shop (the canonical audience), a frontier-lab direct enterprise (when the coding agent is one product line inside a large contract), and a sub-agent swarm (when each swarm worker is itself a coding agent and needs its own attribution leaf).
Quickstart
import { init } from "@pleach/observe";
import { memory } from "@pleach/observe/destinations";
import { createInMemorySandboxProvider } from "@pleach/sandbox/testing";
import { instrumentedCodingAgent } from "@pleach/recipes/coding-agent";
const dest = memory();
init({ destination: dest });
const agent = instrumentedCodingAgent({
sandboxProvider: createInMemorySandboxProvider({ execHandlers: new Map() }),
subagent: "coding-agent",
});
await agent.start();
try {
const result = await agent.executeStep({
userMessage: "fix the failing test in lib/parser.ts",
stepId: "step-1",
});
console.log(result.ok, dest.rows); // rows tagged subagent: "coding-agent"
} finally {
await agent.stop();
}Regression-locked end-to-end in
__tests__/cross-sku/codingAgentInstrumentedByObserve.integration.test.ts.
What it does
The recipe forwards every config field except subagent to
createCodingAgentRuntime({...}) from
@pleach/coding-agent, then wraps the returned runtime's
executeStep(...) in observeSubagent(subagentName).run(...).
The other lifecycle methods (start, stop,
getContextSnapshot) pass through unchanged.
Compose freely with outer scopes — the recipe's inner
subagent(subagent) scope nests beneath any outer scope:
import { subagent } from "@pleach/observe";
await subagent("tenant-abc").run(async () => {
await agent.executeStep({ ... }); // path: ["tenant-abc", "coding-agent"]
});This is the load-bearing composition pattern for multi-tenant coding-agent shops: the tenant is the outer scope, the coding-agent role is the inner scope, and the attribution path joins cleanly to billing on either column.
Config reference
interface InstrumentedCodingAgentConfig extends CodingAgentRuntimeConfig {
/**
* Sub-agent attribution label applied to all rows recorded
* inside each executeStep(...) call. Defaults to
* "coding-agent". Set to false to disable the ALS scope
* (useful when the buyer is composing the recipe inside
* their own outer scope).
*/
subagent?: string | false;
}
interface InstrumentedCodingAgent extends CodingAgentRuntime {
/**
* The underlying coding-agent runtime — escape hatch for
* advanced consumers (dispose, snapshot, ...).
*/
readonly runtime: CodingAgentRuntime;
}The remaining config fields (sandboxProvider,
orchestratorConfig, plan / tool / system-prompt overrides,
…) are forwarded to createCodingAgentRuntime from
@pleach/coding-agent/runtime. See the SKU's own docs for
the full surface.
Common gotchas
- Nesting order matters. When wrapping inside an outer
scope, the attribution path is
[outer, subagent]— the outermost wrap is the leftmost path element. Reverse it and you'll group billing rows by sub-role instead of by tenant. Pick the order to match your downstreamGROUP BY. init({destination})is a process-singleton. Call once at app boot, before driving anyexecuteStep. The recipe does NOT callinititself; without it thesubagent(...).run(...)wrap is a no-op pass-through.subagent: falsedisables the inner scope. Use this when you are composing the recipe inside your own outer scope and don't want the recipe's default"coding-agent"label appearing on rows. Outer scopes still apply.sandboxProvideris consumer-owned. The recipe is sandbox-agnostic — passcreateInMemorySandboxProviderfrom@pleach/sandbox/testingfor tests, or a real provider in production. The provider is forwarded unchanged.- Subpath import is load-bearing. Import from
@pleach/recipes/coding-agent, not the root barrel. - Three optional peers.
@pleach/coding-agent,@pleach/sandbox, and@pleach/observeare all OPTIONAL peers of@pleach/recipes. Install the trio alongside this recipe.
See also
@pleach/recipesoverview — every recipe in one page.@pleach/coding-agent— the underlying runtime,CodingAgentRuntimeConfig,executeStepshape.@pleach/sandbox— the sandbox provider contract the recipe forwards.@pleach/observe—init,recordCall,subagent, the four destinations.Coding agent— full-system pattern.Subagents— the underlying attribution primitive.
BYOK observability
How to drop @pleach/observe into an existing OpenAI / LangChain / hand-rolled agent loop and write one auditable row per LLM call to your own Postgres, Supabase, or OpenTelemetry collector. No @pleach/core commitment required.
verticalAgent
Chatbot pre-wired with your domain plugin. You write the HarnessPlugin (citation extraction, entity recognition, post-tool-tier enrichment, custom event channels); the recipe wires it into createPleachRuntime so every ask() turn picks up your domain logic without manual plumbing.