Skip to content

Name the cron job on the model requests of its sessions - #662

Closed
alex-clickhouse wants to merge 2 commits into
mainfrom
alex/inference-cron-job-header
Closed

alex-clickhouse wants to merge 2 commits into
mainfrom
alex/inference-cron-job-header

Conversation

@alex-clickhouse

Copy link
Copy Markdown
Collaborator

What

In external mode, each model request of a session that a cron job started carries one X-Nerve-Inference-Cron-Job header. The value names the job. This covers isolated and persistent cron jobs, and the leg session of a workflow run that a cron job started.

How

  • nerve/inference_principal.py adds the header next to X-Nerve-Inference-Principal and X-Nerve-Inference-Trigger, with the same rules: local mode sends none, Nerve removes an inherited line from ANTHROPIC_CUSTOM_HEADERS, and a CLI process keeps the value for its whole life. The engine reads the job from the session's origin_detail.cron_job and gives it to the Claude backend in SessionSpec.inference_cron_job.
  • Cron job IDs are free text. cron_job_tag_value() changes an ID to 1 to 64 bytes of ASCII letters, digits, ., _ and -: each run of other characters becomes -, and - at the ends is removed. An empty result becomes job-<8 hex of SHA-256>, and a result over 64 bytes becomes its first 55 bytes, - and the hash. So every job gets a value, and two IDs give the same value only rarely.
  • The leg session of a workflow run with created_by cron:<job> records the job in origin_detail. Its origin line reads "From cron job ", and its origin link still opens the workflow runs: originLink checks workflow_run before cron_job.
  • Nerve's own model calls, "Run later" sessions and Codex send no cron job header. Nerve does not prove the value.

Why

A proxy in front of the model API can attribute model usage by person and by trigger, but not by cron job. Scheduled work is a large part of an agent's model usage, so the cost of each job must be visible.

Testing

  • .venv/bin/pytest tests/ (6 parts, temporary HOME): 8686 passed, 39 skipped, total equal to the 8725 collected
  • cd web && npm run build && npm test: build clean, 1892 tests passed
  • New tests: the transform and its bounds, the CLI environment, isolated, persistent and workflow cron sessions, local mode, and Nerve's own clients (no cron job header)

🤖 Generated with Claude Code

In external mode, a proxy in front of the model API can attribute model
usage by person and by trigger, but not by cron job. Scheduled work is a
large part of an agent's model usage, so the cost of each job must be
visible.

The model requests of a session that a cron job started now carry one
X-Nerve-Inference-Cron-Job header with the job, through
ANTHROPIC_CUSTOM_HEADERS, under the same rules as the principal and
trigger headers. Job IDs are free text, so Nerve changes the ID to 1 to
64 bytes of ASCII letters, digits, '.', '_' and '-', with a short hash
when the ID has no such character or is too long. The leg session of a
workflow run that a cron job started records the job in its origin, so
its requests name the job too. The origin link of such a session opens
the workflow runs, as for other workflow legs.

Nerve's own model calls, Run later sessions and Codex send no cron job
header.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@alex-clickhouse
alex-clickhouse marked this pull request as ready for review November 10, 2026 05:46
@alex-clickhouse

Copy link
Copy Markdown
Collaborator Author

@copilot resolve the merge conflicts in this pull request

…ob-header

# Conflicts:
#	docs/config.md
#	nerve/agent/backends/base.py
#	nerve/agent/backends/claude.py
#	nerve/agent/engine.py
#	nerve/inference_principal.py
#	tests/test_inference_principal.py

Co-authored-by: alex-clickhouse <237136924+alex-clickhouse@users.noreply.github.com>

Copilot AI commented Nov 10, 2026

Copy link
Copy Markdown

@copilot resolve the merge conflicts in this pull request

Resolved and merged main, preserving both the cron-job and origin headers. Merge commit: 1d92fd3. Focused pytest could not run because pytest is not installed in the checkout.

@nerveai

nerveai commented Nov 10, 2026

Copy link
Copy Markdown

Nerve PR review · ⏸ Hold: mostly a duplicate of #658, needs a decision

Since #658 merged, every isolated and persistent cron session already sends X-Nerve-Inference-Origin: cron=<job>. The merge commit 1d92fd3 keeps both mechanisms. So each cron session now names its job twice, in two encodings (cron=Morning_briefing and Morning-briefing), and a proxy has two contracts for one fact. This PR adds two things that #658 lacks. A workflow-run leg that a cron job started names the job; #658 sends only workflow=<run id> for it. A long or non-ASCII job ID gets a hash suffix instead of a plain cut at 56 bytes.

Completeness: tests cover the transform, the CLI env, and cron, persistent and workflow sessions. The docs are in config.md and accounts.md. Claude only, like #658.

Before merge:

  • Keep one header for the cron job. Either drop X-Nerve-Inference-Cron-Job (nerve/inference_principal.py:69) and move the leg case and the hash rule into origin_tag, or document why the proxy needs both.
  • _leg_origin (nerve/workflows/service.py:457) matches only created_by cron:<job>. A run that a cron session starts with workflow_run_start (session:cron:…, nerve/agent/tools/handlers/workflow_runs.py:202) names no job, but Nerve's per-job usage counts it (nerve/db/usage.py:310). So the proxy's per-job totals would come out lower than the Diagnostics page.

Checked: 397 tests passed in an offline sandbox at 1d92fd3 (inference_principal, session_origin, workflow_*, claude_transport). CI had not reported at this head (4/4 ok at ab85e8a). Read the diff, #658's origin_tag and the usage-by-cron-job query. Frontend not run locally · automated review by nerveai — mention @nerveai to ask a follow-up.

@alex-clickhouse

Copy link
Copy Markdown
Collaborator Author

Closed: #658 (merged) covers this change. Nerve sends the cron job in X-Nerve-Inference-Origin as cron=<job id>, and the inference proxy tags it as nerve_origin:cron=<job id> (ClickHouse/nerve-private#743). The branch alex/inference-cron-job-header is kept.

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.

4 participants