# instrumentedCodingAgent (/docs/recipes/instrumented-coding-agent)



`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 [#quickstart]

```ts
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 [#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:

```ts
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 [#config-reference]

```ts
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 [#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 downstream
  `GROUP BY`.
* **`init({destination})` is a process-singleton.**
  Call once at app boot, before driving any `executeStep`.
  The recipe does NOT call `init` itself; without it the
  `subagent(...).run(...)` wrap is a no-op pass-through.
* **`subagent: false` disables 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.
* **`sandboxProvider` is consumer-owned.** The recipe is
  sandbox-agnostic — pass `createInMemorySandboxProvider`
  from `@pleach/sandbox/testing` for 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/observe` are all OPTIONAL
  peers of `@pleach/recipes`. Install the trio alongside
  this recipe.

## See also [#see-also]

* [`@pleach/recipes` overview](/docs/recipes-pleach-recipes#instrumentedcodingagent)
  — every recipe in one page.
* [`@pleach/coding-agent`](/docs/coding-agent) — the
  underlying runtime, `CodingAgentRuntimeConfig`,
  `executeStep` shape.
* [`@pleach/sandbox`](/docs/sandbox) — the sandbox provider
  contract the recipe forwards.
* [`@pleach/observe`](/docs/observe) — `init`, `recordCall`,
  `subagent`, the four destinations.
* [`Coding agent`](/docs/coding-agent) — full-system
  pattern.
* [`Subagents`](/docs/subagents) — the underlying
  attribution primitive.
