Skip to content

feat(proxy): expanded fields and new usage events (OTLP analytics pipeline) - #609

Draft
dzhalai wants to merge 15 commits into
codemie-ai:mainfrom
dzhalai:EPMCDME-15301_stage-1-1
Draft

dzhalai wants to merge 15 commits into
codemie-ai:mainfrom
dzhalai:EPMCDME-15301_stage-1-1

Conversation

@dzhalai

@dzhalai dzhalai commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

Summary

Changes

Impact

Checklist

  • Self-reviewed
  • Manual testing performed
  • Documentation updated (if needed)
  • No breaking changes (or clearly documented)

? resolvePromptStory(ctx, rawPrompt)
: { storyId: ctx.story?.storyId ?? '', storySource: ctx.story?.storySource ?? '' };

const { AgentRegistry } = await import('../../../../../../agents/registry.js');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this dynamic import? Do we actually have problems with circular dependencies?

import AgentRegistry from '@/agents/registry.js'

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not really needed. Updated.

@dzhalai dzhalai Oct 6, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reverted due to CI issues.

Comment thread src/providers/plugins/sso/proxy/plugins/otlp-spool/forwarder.ts Outdated
Comment on lines +268 to +277
} catch (err) {
// OtlpAgentAdapter.prepareAnalyticsFields's "must never throw" contract is only a doc
// comment — a future/alternate adapter implementation that violates it must not abort
// every remaining record in this forward tick.
const msg = err instanceof Error ? err.message : String(err);
logger.debug(
'[otlp-forwarder] prepareAnalyticsFields threw',
...sanitizeLogArgs({ agentName: spoolData.agentName, err: msg })
);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of this ugly catch, we can swallow all errors inside the prepareAnalyticsFields

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Such approach guarantees that new implementations (new plugins) will not break this execution in case of any exceptions.

Comment on lines +250 to +261
// Read the record's OWN untruncated prompt text here, before
// `limitHookPayload()` below produces its own truncated `limited` copy.
// `limitHookPayload` never mutates `hookEvent` itself (it builds a fresh
// `{ ...hookEvent }` copy), so this is still the full original string —
// used ONLY to feed the marker/mention regex tiers below; the matched
// ticket id (a short string) is all that ever reaches the output, never
// this raw text itself.
const rawPrompt = typeof hookEvent['prompt'] === 'string' ? hookEvent['prompt'] : '';
const promptStory =
hookName === 'UserPromptSubmit'
? resolvePromptStory(ctx, rawPrompt)
: { storyId: ctx.story?.storyId ?? '', storySource: ctx.story?.storySource ?? '' };

@vld-m vld-m Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's extract it into something like resolveStory(ctx, hookEvent). Like we do for the resolveStoryOnce and resolveIdentityOnce.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, I don't think I understand why do we need both resolveStoryOnce + resolvePromptStory + these snippet's logic. Everything looks related to the story resolution. Can't we combine/simplify the approach?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made some renamings.
The idea of this logic is to send the most actual info about story if it is overridden in another user prompt (e.g., a developer started work on story A, but in the middle of a session switched to story B and mentioned it in a prompt). This is how I understand this use case from the requirements.

Comment thread src/providers/plugins/sso/proxy/plugins/otlp-spool/forwarder.ts Outdated
userEmail: resolveUserEmail(credentials),
git: {},
identity: {},
story: {},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these empty objects (including git) looks sus. Can we do all

    await resolveGitInfo(ctx, cwd);
    await resolveIdentityOnce(ctx, cwd);
    await resolveStoryOnce(ctx, cwd);

right here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cwd is not known here and call order of three methods is important.

Comment thread src/providers/plugins/sso/proxy/plugins/otlp-spool/forward-context.ts Outdated
Comment thread src/agents/plugins/claude-code-otlp/claude-code-otlp.plugin.ts Outdated
}

if (event.hookEventName === 'SessionEnd') {
void (async () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do you prefer void over plain await drop? like we do in existing forwardToSpool?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment might be not actual after rework. Please, double check and let me know if you have concerns.

Comment thread src/agents/plugins/claude-code-otlp/transcript/orchestrator.ts Outdated
Comment thread src/agents/plugins/claude-code-otlp/claude-code-otlp.plugin.ts Outdated

private async evaluate(rawEvent: string): Promise<ForwardDecision> {
const event = toBaseClaudeCodeHookEvent(JSON.parse(rawEvent));
private async evaluate(rawEvent: string): Promise<ForwardDecision> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the idea behind this evaluate pipeline is to make a ForwardDecision. Block the event or forward it to spool. So we have two consequences:

  • I'd better stick to the following approach
if (event.hookEventName === '_EventName_') {
  return await on_EventName_(event); // <- decision to forward or not + (create a synthetic event OR enhance the existing one)
}

  • Use one handler per event name instead of the event.hookEventName === 'Stop' || event.hookEventName === 'PreCompact' || ... chains.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated.

Comment thread src/providers/plugins/sso/proxy/plugins/otlp-spool/forward-context.ts Outdated
Comment on lines +36 to +38
if (!event.sessionId) {
return { decision: 'forward', payload: [rawEvent] };
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this scenario possible? and why do we want to process such sessions?

vld-m and others added 2 commits October 6, 2026 21:24
Replace the mixed if/switch dispatch with one if-chain and one handler
per hook event, each returning its own ForwardDecision directly. Drop
the shared fallback sink, collapse the raw/parsed object duplication by
extending BaseClaudeCodeHookEvent, rename the orchestrator collectors,
and split client-version caching from resolution.

Generated with AI

Co-Authored-By: codemie-ai <codemie.ai@gmail.com>
Add docs/ARCHITECTURE-OTLP-PLUGIN.md: the agent-agnostic
OtlpAgentAdapter contract, the evaluate()/ForwardDecision dispatch
pattern, how to add a new hook event or a new adapter, and the current
otlp-spool completeness-gating/draining behavior (with planned
extensions clearly flagged as not-yet-implemented). Index it from
AGENTS.md's Agent Plugins table and add a top-level OTel (OTLP)
Ingestion section pointing new contributors at it.

Generated with AI

Co-Authored-By: codemie-ai <codemie.ai@gmail.com>

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants