Skip to content

feat(analyst): a TraceAnalysisEngine a consumer can build without Python, so createTraceAnalyst is reachable #713

Description

@drewstone

At agent-eval 0.171.0 createTraceAnalyst(definition, options) requires options.engine: TraceAnalysisEngine, and that field is not optional (dist/default-registry-B0s2zU-s.d.ts:79).

Grepping the whole installed dist for a constructor returns exactly one:

$ grep -h "): TraceAnalysisEngine" dist/*.d.ts
dist/index-DT73JraI.d.ts:178  declare function createDspyRlmTraceEngine(options: DspyRlmTraceEngineOptions): TraceAnalysisEngine;

DspyRlmTraceEngineOptions needs runner?: ExternalOptimizerRunnerCommand ("Python command used to load agent-eval-rpc[dspy]. Default: python", index-DT73JraI.d.ts:172) plus a caller-owned ExternalOptimizerModelCall and a recordExecution sink.

Measured consequence, run against the installed packages:

buildDefaultAnalystRegistry().list()
  -> [{ id: "efficiency-behavioral", version: "2.0.0", cost: { kind: "deterministic" } }]

analystsFromRegistry(thatRegistry)     // @tangle-network/agent-runtime/kernel 0.185.2
  -> throws: analystsFromRegistry: "failure-mode", "intent-divergence", "knowledge-gap",
     "knowledge-poisoning", "improvement" are not registered;
     the registry has "efficiency-behavioral"

So a consumer that already has a model seam — an AgentProfile on a bridge or a sandbox seat, driven through agent-runtime streamAgentTurn — cannot build a registry that satisfies analystsFromRegistry, and cannot reach any of the five DEFAULT_TRACE_ANALYST_KINDS. The documented composition supervise(profile, task, { analysts: analystsFromRegistry(registry) }) (agent-runtime docs/canonical-api.md line 183) has no reachable path unless the consumer stands up Python.

Ask

Export a TraceAnalysisEngine constructor whose model seam is a plain call — an LlmCallRequest -> LlmCallResult function, or a ChatClient — so a consumer can supply its own executor. Something equivalent to:

declare function createCallTraceEngine(opts: {
  call: (req: LlmCallRequest) => Promise<LlmCallResult>
  recordExecution?: (row: unknown) => void
}): TraceAnalysisEngine

Then buildDefaultAnalystRegistry({ engine }) registers the five default kinds, analystsFromRegistry stops throwing, and a consumer deletes its hand-rolled lens.

Why we care, with the number

discovery-lab carries tools/analysts.mjs, 496 lines, whose createTraceBehaviorAnalyst hand-builds the { kinds, run } lens shape because of exactly this. About 150 of those lines are a prompt renderer over toolSpansToTraceAnalysisStore output that the engine would own. It is the last blocker on deleting the file.

Second, smaller gap in the same area

The one deterministic analyst that IS reachable, behavioralAnalyst, emits only monotonic-input-growth | output-length-decay | single-tool-dependency | no-self-verification (default-registry-B0s2zU-s.d.ts:7). There is no repeated-call / stuck-loop signal on the lens path. repeatedActionDetector has that invariant but is a StreamingDetector over pre-hashed fingerprints, not a store reader; computeToolUseMetrics and toolWasteView take a TraceStore while the lens contract hands a TraceAnalysisStore, and their duplicateRate is set membership, not a consecutive run. A deterministic, $0 loop panel over a TraceAnalysisStore would let a consumer delete another ~110 lines.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions