Model Invocation Audit Schema — v1.1¶
Purpose¶
Every time any eco|monetize™ agent spawns a connection to a local or remote model — Ollama, Claude API, OpenAI, Gemini, HuggingFace, or any future provider — it MUST log one record to the central audit log. This creates a platform-reportable history of all model activity across all agents.
Why this matters: Without this, there is no way to answer "how many model calls did we make, to what models, with what cost and latency, and did any fail?" as the agent fleet scales. This schema is the foundation for cost reporting, reliability monitoring, and governance audits.
Central audit log location¶
- Format: JSON Lines — one JSON object per line, newline-delimited
- Append-only — never overwrite, never truncate
- Concurrent-write safe — each agent opens the file in append mode
- Queryable with any JSON Lines reader,
jq, or Python
Log rotation policy¶
To prevent unbounded file growth, mining.mind archives the log monthly:
- Active log:
model-invocations.jsonl(current month, all writes go here) - Archive:
archive/YYYY-MM.jsonl(prior months, read-only) - Rotation runs on the first session of each new calendar month
- No record is ever deleted — archiving is a move, not a truncation
Schema — one record per model invocation¶
{
"ts_start": "2026-04-21T10:32:00.000Z",
"ts": "2026-04-21T10:32:47.200Z",
"session_id": "20260421-103200-a3f9b1",
"agent": "mining.mind",
"script": "council-review-launcher.py",
"host": "ricks-macbook-pro",
"model_id": "llama3.3-70b",
"model_name": "llama3.3:70b",
"provider": "Meta (Llama)",
"provider_type": "local",
"purpose": "council-review",
"topic": "cdo-prompt-engineering-retrofit",
"mission_id": "M-2026-0418-cdo-prompt-engineering-retrofit",
"tokens_in": 1840,
"tokens_out": 612,
"latency_s": 47.2,
"status": "success",
"error_msg": null,
"output_file": "/Claude/operations/reports/council-reviews/2026-04-21-cdo-prompt/llama3.3-70b-review-2026-04-21.md"
}
Field definitions¶
| Field | Type | Required | Description |
|---|---|---|---|
ts_start |
ISO 8601 UTC | yes | Timestamp when the API call was initiated (before network/inference time) |
ts |
ISO 8601 UTC | yes | Timestamp when the response was fully received (call completion) |
session_id |
string | yes | Unique ID for the script execution. All calls from one run share this ID. Format: YYYYMMDD-HHMMSS-{6-char hex} |
agent |
string | yes | Agent ID (mining.mind, code.platform, etc.) |
script |
string | yes | Script or tool name that made the call |
host |
string | yes | Machine hostname (socket.gethostname()). Distinguishes travel laptop from Mac Studio for latency analysis |
model_id |
string | yes | Short canonical model identifier used as a key (e.g. llama3.3-70b, gpt-4o) |
model_name |
string | yes | Exact model name as passed to the provider (e.g. llama3.3:70b, gpt-4o) |
provider |
string | yes | Human-readable provider name (Meta (Llama), OpenAI, Anthropic, etc.) |
provider_type |
enum | yes | local (Ollama / on-device) or external (API call to remote provider) |
purpose |
string | yes | What the call was for — must be an approved slug from the Purpose Slugs table below |
topic |
string | yes | Kebab-case slug identifying the artifact or task. No proper nouns, no customer names, no deal identifiers — see PII policy below |
mission_id |
string | no | Mission ID if this call is part of a Mission (M-YYYY-MMDD-slug). Null if not Mission-bound |
tokens_in |
int | no | Input tokens (prompt). Null if provider does not report or if streaming (see Streaming section) |
tokens_out |
int | no | Output tokens (completion). Null if provider does not report or if streaming |
latency_s |
float | yes | Wall-clock seconds from ts_start to ts. Always present, even on error |
status |
enum | yes | success, error, skipped |
error_msg |
string | no | Error detail if status is error or skipped. Null on success |
output_file |
string | no | Absolute path to the primary output file produced by this call. Null if no file output |
PII policy — topic field¶
The topic field is operational metadata stored in an unencrypted log. It must never contain:
- Customer names, company names, or contact information
- Deal names or opportunity identifiers
- Any string that could identify a specific individual or organization
Use: kebab-case slugs describing the type of artifact or task — cdo-prompt-engineering-retrofit, weekly-positioning-review, eri-framework-v2, meeting-signal-extraction.
Do not use: ron-davis-hpe-meeting, acme-corp-deal-review, john-smith-objections.
If a call is for a specific meeting or customer artifact, encode the reference in mission_id or output_file — those paths already have access controls — not in topic.
Provider token reporting — by provider¶
| Provider | tokens_in source |
tokens_out source |
|---|---|---|
| Ollama | prompt_eval_count in response body |
eval_count in response body |
| OpenAI (GPT-4o) | usage.prompt_tokens |
usage.completion_tokens |
| Google (Gemini) | usage_metadata.prompt_token_count |
usage_metadata.candidates_token_count |
| Anthropic (Claude API) | usage.input_tokens |
usage.output_tokens |
| HuggingFace Inference | provider-dependent — log null if unavailable |
same |
If a provider does not return token counts, log null. Do not estimate or infer.
Streaming calls¶
When using streaming APIs (stream: true in Ollama, stream=True in OpenAI SDK):
latency_s= time-to-complete (time from call start to last chunk received, not time-to-first-token)tokens_in/tokens_out= accumulate from streaming chunks if the provider emits usage per chunk. If not available from the stream, lognull— do not estimatets_startandtsfollow the same convention as non-streaming calls
The reference implementation (council-review-launcher.py) uses non-streaming mode and is the simpler pattern. Prefer non-streaming for batch/governance workloads where latency-to-first-token doesn't matter.
Approved purpose slugs¶
Use these consistently so queries work across agents:
| Slug | Use for |
|---|---|
council-review |
Multi-model council review of an artifact |
meeting-transcription |
Whisper or model-based audio transcription |
signal-extraction |
Extracting structured signals from meeting transcripts or docs |
summarization |
Condensing documents, transcripts, or long-form content |
qlora-training |
Fine-tuning / QLoRA training runs |
rag-query |
RAG retrieval + synthesis queries |
eval |
Evaluation harness runs |
embedding |
Embedding generation (nomic-embed-text, etc.) |
general |
Any call not covered by the above |
Add new slugs by updating this table and bumping the schema minor version. No ADR required for slug additions; ADR required for field additions or type changes.
Implementation guide for agent developers¶
Python snippet — copy verbatim¶
import json, socket, time, uuid
from datetime import datetime, timezone
from pathlib import Path
import os
# Resolve vault root from env var — update ECO_VAULT_ROOT if your home dir differs
VAULT_ROOT = Path(os.environ.get("ECO_VAULT_ROOT", "/Users/rhartley/Claude"))
AUDIT_LOG = VAULT_ROOT / "operations/logs/model-invocations/model-invocations.jsonl"
def log_invocation(
session_id, agent, script, model_id, model_name, provider,
provider_type, purpose, topic, mission_id,
tokens_in, tokens_out, ts_start, ts_end, status, error_msg, output_file
):
latency_s = round((ts_end - ts_start), 2)
record = {
"ts_start": datetime.fromtimestamp(ts_start, tz=timezone.utc).isoformat(),
"ts": datetime.fromtimestamp(ts_end, tz=timezone.utc).isoformat(),
"session_id": session_id,
"agent": agent,
"script": script,
"host": socket.gethostname(),
"model_id": model_id,
"model_name": model_name,
"provider": provider,
"provider_type": provider_type,
"purpose": purpose,
"topic": topic,
"mission_id": mission_id,
"tokens_in": tokens_in,
"tokens_out": tokens_out,
"latency_s": latency_s,
"status": status,
"error_msg": error_msg,
"output_file": output_file,
}
AUDIT_LOG.parent.mkdir(parents=True, exist_ok=True)
with AUDIT_LOG.open("a", encoding="utf-8") as f:
f.write(json.dumps(record) + "\n")
Do not rename fields. Field naming must be consistent across all agents for the audit log to be queryable. If a field doesn't apply to your use case, pass None — do not omit it.
Timing pattern¶
t_start = time.time() # wall-clock float, before API call
# ... make your API call ...
t_end = time.time() # after response received (or error caught)
log_invocation(..., ts_start=t_start, ts_end=t_end, ...)
Use time.time() (not time.monotonic()) so timestamps align with the ISO UTC fields. Always capture both timestamps even on error — latency_s on a failed call is diagnostic.
Environment variable¶
Set ECO_VAULT_ROOT in your shell profile or .env if your vault root differs from /Users/rhartley/Claude:
Querying the audit log¶
Count calls by model¶
Total tokens by provider type (success only)¶
jq 'select(.status == "success") | {provider_type, tokens_in, tokens_out}' model-invocations.jsonl \
| jq -s 'group_by(.provider_type) | map({provider_type: .[0].provider_type, total_in: map(.tokens_in // 0) | add, total_out: map(.tokens_out // 0) | add})'
All calls for a Mission¶
Error rate by agent¶
jq -r '[.agent, .status] | @tsv' model-invocations.jsonl \
| awk '{counts[$1"_"$2]++} END {for (k in counts) print counts[k], k}' | sort -rn
Average latency by model and host¶
jq -r '[.model_id, .host, (.latency_s | tostring)] | @tsv' model-invocations.jsonl \
| awk '{key=$1"/"$2; sum[key]+=$3; count[key]++} END {for (k in sum) printf "%.1fs avg %s (%d calls)\n", sum[k]/count[k], k, count[k]}' | sort
Local vs external call split¶
Compliance requirement¶
Any agent or script that calls a model MUST log to this file. This is not optional. Failure to log is a Section 6E SOP breach (SEV3).
Enforcement: - The CDO (Dave) owns compliance enforcement - mining.mind produces a weekly model-call digest from the JSONL as part of Friday operations reporting — CDO receives a pre-digested summary (calls by agent, errors, total tokens, latency p50/p95) rather than raw file access - The CDO's Friday Close (per CLAUDE.md Section 12) includes a standing review of the digest
Reference implementation¶
/Claude/scripts/agents/council-review-launcher.py — mining.mind's council review launcher. First agent to implement this schema (2026-04-21). Use it as the reference implementation.
Changelog¶
| Version | Date | Changes |
|---|---|---|
| v1.1 | 2026-04-21 | Peer review pass: added ts_start + host fields; added PII policy for topic; added log rotation policy; added streaming guidance; added summarization purpose slug; moved snippet to time.time() + ECO_VAULT_ROOT env var; added weekly digest to compliance enforcement |
| v1.0 | 2026-04-21 | Initial schema (mining.mind) |
Owner: mining.mind Executive sponsor: cdo (Dave) Approved by: ceo (Rick Hartley) Effective: 2026-04-21 Version: 1.1