Epoch

tech.kyanitelabsPublished by kyanitelabs.techv0.6.0Updated Oct 30, 2026

Epoch time estimation MCP server for PERT, COCOMO, Monte Carlo, calibration, cost, and risk.

Overview

AI-generated overview

Local MCP server that gives an assistant 26 tools for time math, project duration estimation, schedule risk and LLM token cost comparison.

What you can do
  • Get current time, convert timezones, parse durations and do date arithmetic
  • Add or count business days with US, UK, FR, DE and JP holiday calendars
  • Estimate effort and duration with PERT, COCOMO II, sprint velocity, critical path and Monte Carlo simulation
  • Forecast from reference classes, classify free-text task descriptions and score schedule risk
  • Map token budgets to wall-clock time and compare cost and speed across LLM models
  • Record actual hours and review estimation accuracy trends and feedback-loop health
When to use it
Useful when an assistant is asked how long a task or project will take, whether a deadline is realistic, how many working days remain, or which model is cheapest for a token budget. Also fits teams that want to log actuals and track estimation accuracy over time.
Requirements
Runs locally over stdio via npx from the npm package; Node.js is needed. No account, API key or authentication is required. Optional environment variables configure an HTTP transport, port, bind address, data directory, rate limiting, CORS and telemetry.
Before you install
Recording actuals writes estimate and feedback data to a local data directory. Anonymous telemetry is off by default and must be opted into by a human, either with the CLI or by setting EPOCH_TELEMETRY; it shares task type, complexity, tool name, estimated and actual hours, ratio and date. Enabling HTTP transport with permissive CORS or proxy trust widens exposure.

Installation

In SourceWeft

  1. Open Epoch in the dashboard and add it to a workspace.
  2. Enable the server for the chats that should use its tools.

Desktop only via STDIO. STDIO servers start a local process, so they need the SourceWeft desktop host.

Other MCP clients

Follow the launch instructions in the repository.

README

[Epoch — estimation MCP server for AI agents: PERT, COCOMO II, Monte Carlo, schedule-risk tools]

Epoch -- Time Estimation MCP Server

[epoch icon]

Epoch is a time estimation MCP server that helps engineering leads, agents, and planners who need calibrated duration estimates produce PERT/reference-class estimates and record actuals.

TL;DR: Epoch — time estimation MCP server. Best for engineering leads, agents, and planners who need calibrated duration estimates.

[CI] [License: Apache-2.0] [MCP] [npm version] [MCP Registry]

Epoch helps AI agents understand time.

AI can write code, analyze data, and build apps -- but it can't tell you how long something will take, what it'll cost, or whether a deadline is realistic. Epoch fixes that.


Show Me

You: "How long will a 15,000 line project take with a small team?"
Claude (using Epoch):  COCOMO II nominal: 100 person-months (traditional team)  AI-adjusted: 9 person-months (LLM-assisted workflow)  Schedule risk: medium (MAPE 15%, based on 28 historical features)  Cost estimate (Claude Sonnet): ~$340 in API tokens
You: "How many working days between now and launch?"
Claude (using Epoch):  Start: May 1, 2026  End: July 15, 2026  Business days: 54  Weekend days: 16  Holidays: 1 (Independence Day)  Total calendar days: 75
You: "Which AI model should we use for a 50k-token job?"
Claude (using Epoch):  gemini-2.0-flash:  $0.02 -- fast, budget-friendly  gpt-4o-mini:       $0.02 -- fast, budget-friendly  gemini-2.5-pro:    $0.37 -- standard, high quality  claude-sonnet-5:    $0.57 -- standard, high quality  Recommendation: gemini-2.0-flash for cost, claude-sonnet-5 for quality

Why Epoch?

Every AI agent hallucinates timelines. "This should take about 2 hours" becomes 2 days. Epoch gives AI grounded, data-driven estimates instead of guesses. It packages established estimation methods (PERT, COCOMO II, Monte Carlo, reference class forecasting) into 26 tools any AI can call -- so your assistant stops guessing and starts calculating.

Works out of the box. Epoch ships with a bundled reference database: task-type correction factors and scope baselines from the maintainer's recorded estimate-vs-actual history plus curated public research, and tool-usage/latency telemetry samples (117,791 across the bundled canary-test and session benchmarks) that keep the token-time bridge grounded. You get reasonable estimates from day one — no data collection or account setup required. If you choose to record your actuals, Epoch's self-improvement engine learns your patterns and gets even more precise over time.

What is MCP?

MCP (Model Context Protocol) is how AI assistants like Claude connect to external tools. Think of it like a plugin system -- you add Epoch with one command, and suddenly your AI assistant can estimate timelines, calculate business days, compare model costs, and predict whether your project will finish on time.

Quick Start

30-second setup -- works in Claude Code, Cursor, VS Code, and Windsurf:

bash
claude mcp add epoch -- npx -y @kyanitelabs/[email protected]

That's it. Your AI assistant now has 26 time estimation tools.

Or add it to your project's .mcp.json:

json
{  "mcpServers": {    "epoch": {      "command": "npx",      "args": ["-y", "@kyanitelabs/[email protected]"]    }  }}

Limit Epoch tools in Hermes

Use Hermes tools.include to expose only these example tools:

yaml
mcp_servers:  epoch:    tools: { include: [pert_estimate, cocomo_estimate] }

Agent Skill

Epoch also ships a public agent skill at skills/epoch/SKILL.md. Use $epoch in compatible agent hosts when you want the agent to choose the right Epoch MCP or CLI workflow for time estimates, business-day math, model-cost comparison, schedule risk, and estimate-vs-actual feedback.

What Can Epoch Do?

What you wantWhat Epoch doesNo jargon
"How long will this take?"Gives you a realistic estimate with best/worst case rangesEstimates
"Can we hit this deadline?"Tells you if your timeline is realistic or riskySchedule risk
"How much will the AI calls cost?"Calculates token costs across 16 AI models side-by-sideCost comparison
"How many business days between now and launch?"Counts days excluding weekends and holidays (5 countries)Calendar math
"Are our estimates getting better?"Tracks your accuracy over time and auto-correctsSelf-improving
"What model should we use?"Compares speed, cost, and quality across all major AI modelsModel comparison

Technical Reference

Everything below is for developers who want to understand the internals, use the CLI or REST API, or contribute to Epoch.

Architecture

Six-layer design with 26 tools for time estimation, scheduling, cost analysis, and feedback:

LayerPurposeTools
1. Core TemporalTime, timezones, duration, date mathget_current_time, convert_timezone, parse_duration, time_math
2. Calendar MathBusiness days, holidays (US/UK/FR/DE/JP)add_business_days, count_business_days
3. EstimationPERT, COCOMO II, sprint, CPM, Monte Carlopert_estimate, cocomo_estimate, sprint_forecast, critical_path, monte_carlo_schedule
4. AnalyticsReference class, context classification, calibration, token-time bridge, wait-mechanism deadlinesreference_class_estimate, estimate_from_context, calibrate_estimates, token_time_bridge, wait_bound
5. Cost & RiskToken cost, model comparison, accuracy trends, risk, COCOMO validationtoken_cost_estimate, compare_models, accuracy_trend, schedule_risk, cocomo_validate, cocomo_ground_truth
6. FeedbackRecord actuals, track pending estimates, batch operations, health checksrecord_actual, get_pending_estimates, batch_record_actuals, feedback_health

Tool Reference

Layer 1 -- Core Temporal

get_current_time -- Current wall-clock time in any IANA timezone

Input:  { timezone: "America/New_York" }Output: {  iso: "2026-05-01T08:30:00.000-04:00",  humanReadable: "Fri, May 1, 2026, 8:30 AM EDT",  timezone: "America/New_York",  utcOffset: "-04:00"}

convert_timezone -- Convert a timestamp between IANA timezones

Input:  { timestamp: "2026-05-01T12:00:00Z", target_tz: "Asia/Tokyo" }Output: {  iso: "2026-05-01T21:00:00.000+09:00",  timezone: "Asia/Tokyo",  utcOffset: "+09:00",  humanReadable: "Fri, May 1, 2026, 9:00 PM JST"}

parse_duration -- Parse human-readable duration strings

Input:  { duration_string: "2h30m" }Output: {  input: "2h30m",  totalSeconds: 9000,  humanReadable: "2 hours 30 minutes"}

time_math -- Date arithmetic operations

Input:  { operation: "add_days", operands: { start_date: "2026-05-01", days: 7 } }Output: "2026-05-08"

Supported operations: add_days, add_business_days, diff, convert_tz, parse_nl, format_duration

Layer 2 -- Calendar Math

add_business_days -- Add N business days with holiday awareness (US, UK, FR, DE, JP)

Input:  { start_date: "2026-05-01", days: 5, country: "US" }Output: {  startDate: "2026-05-01",  endDate: "2026-05-08",  businessDays: 5,  countryCode: "US",  holidaySupport: "holiday_calendar",  humanReadable: "5 business days from 2026-05-01 to 2026-05-08 (US)."}

count_business_days -- Count business days between two dates

Input:  { start_date: "2026-05-01", end_date: "2026-05-15", country: "US" }Output: {  startDate: "2026-05-01",  endDate: "2026-05-15",  businessDays: 10,  countryCode: "US",  holidaySupport: "holiday_calendar",  humanReadable: "10 business days between 2026-05-01 and 2026-05-15 (US)."}

Both tools always include holidaySupport ("holiday_calendar" when the country has a bundled holiday set — US/UK/FR/DE/JP — otherwise "weekends_only") in successful output; country must match ^[A-Za-z]{2}$ — "USA"-style codes are rejected with a readable error.

Layer 3 -- Estimation

pert_estimate -- PERT three-point estimation with confidence intervals and urgency scoring

Input:  {  optimistic: 2,  most_likely: 4,  pessimistic: 12,  unit: "hours"}Output: {  expected: 5,  variance: 2.78,  stdDeviation: 1.67,  confidence95: [1.67, 8.33],  confidence99: [0, 10],  unit: "hours",  urgencyCategory: "medium",  riskLevel: "high",  humanReadable: "Expected 2.86–7.14 hours (80% confidence interval); point estimate 5 hours (ledger-recorded basis; adjustedEstimate 5 applies the correction factor). No task_type was supplied, so this interval is derived from the PERT variance (optimistic/most_likely/pessimistic spread) instead of empirical data.",  developerProfile: { mode: "ai_native", correctionFactor: 1 },  adjustedEstimate: 5,  rawEstimate: 5,  correctionFactor: 1,  n: 0,  interval: { p50: { lower: 3.87, upper: 6.13 }, p80: { lower: 2.86, upper: 7.14 }, p90: { lower: 2.25, upper: 7.75 }, source: "pert_variance" },  intervalNote: "No task_type was supplied, so this interval is derived from the PERT variance (optimistic/most_likely/pessimistic spread) instead of empirical data.",  basisNote: "Interval and point estimate are on the ledger-recorded basis (raw PERT expected × unit factor). adjustedEstimate (5 hours) additionally applies the correction factor (1) and is display-only — it is never recorded or calibrated against."}

(Example from a fresh install with no accumulated feedback; with ≥5 exclusion-filtered matched pairs for the task type, interval.source becomes "empirical_ratio_quantile", n reports the sample size, and intervalPopulation names the ratio population used.)

cocomo_estimate -- COCOMO II software sizing with LLM-adapted cost drivers

Input:  {  kloc: 15,  reasoning_complexity: 1.2,  context_completeness: 1.0,  transformation_impact: 0.8,  iterative_cycles: 1.5,  human_oversight: 1.2}Output: {  kloc: 15,  personMonthsNominal: 99.9,  personMonthsLlmAdjusted: 8.9,  effortMultipliers: {    reasoning_complexity: 1.2,    context_completeness: 1.0,    transformation_impact: 0.8,    iterative_cycles: 1.5,    human_oversight: 1.2,    product: 1.728  },  developerProfile: { mode: "ai_native", correctionFactor: 1.45 }}

LLM-adapted cost drivers include reasoning complexity, context completeness, transformation impact, iterative cycles, and human oversight requirements.

sprint_forecast -- Sprint velocity forecasting from historical data

Input:  {  backlog_points: 100,  velocity_history: [20, 25, 22, 23],  sprint_length_days: 14,  hours_per_sprint: 80}Output: {  backlogPoints: 100,  averageVelocity: 22.5,  requiredSprints: 4.4,  pessimisticSprints: 4.9,  hoursPerPoint: 3.56,  totalHours: 355.6,  completionDays: 62,  sprintLengthDays: 14,  developerProfile: { mode: "ai_native", sprintVelocityPoints: 80, correctionFactor: 1.45 }}

critical_path -- Critical Path Method with merge-bias adjustment for parallel tasks

Input:  {  tasks: [    { name: "A", duration: 5, predecessors: [] },    { name: "B", duration: 3, predecessors: ["A"] },    { name: "C", duration: 4, predecessors: ["A"] }  ]}Output: {  critical_path: ["A", "C"],  total_duration: 9,  slack_per_task: { A: 0, B: 1, C: 0 },  merge_bias_adjustment: 0}

monte_carlo_schedule -- Monte Carlo simulation with seeded PRNG for deterministic, reproducible results

Input:  {  tasks: [    { name: "A", optimistic: 2, most_likely: 4, pessimistic: 8 },    { name: "B", optimistic: 1, most_likely: 3, pessimistic: 6 }  ],  iterations: 10000}Output: {  p10: "5.9",  p50: "7.91",  p80: "9.39",  p95: "10.75",  riskEvents: [    { description: "Task \"A\" exceeded 1.5x PERT expected in 10% of simulations", probability: 0.1, impactDays: 0.05 },    { description: "Task \"B\" exceeded 1.5x PERT expected in 10% of simulations", probability: 0.1, impactDays: 0.04 }  ],  criticalPathProbability: null}

(criticalPathProbability is null unless a target_hours deadline is supplied — then it is the real P(total ≤ target); riskEvents[].impactDays is per-task expected overrun, sorted by impact.)

Layer 4 -- Analytics

reference_class_estimate -- Reference class forecasting with planning fallacy correction

Input:  {  task_type: "feature",  complexity: 3}Output: {  rawEstimate: 2,  correctedEstimate: 2,  correctionFactor: 1,  sampleSize: 0,  baselineSource: "inferred_scope_medium_real_tasks",  scopeUsed: "medium",  scopeInferred: true,  confidence: "pessimistic",  developerProfile: { mode: "ai_native", estimationMape: 15, underestimationBias: 0.2, correctionFactor: 1 },  adjustedEstimate: 2,  basisNote: "correctedEstimate (2 hours) is the ledger-recorded and displayed basis (rawEstimate × correctionFactor). adjustedEstimate (2 hours) additionally applies the developerProfile factor (1) and is display-only — it is never recorded or calibrated against.",  intervalNote: "Fewer than 5 exclusion-filtered historical \"feature\" reference_class_estimate pairs are available yet, so no empirical confidence interval could be computed."}

(Fresh-install output; with accumulated feedback the correction factor and sample size come from your own matched pairs and the empirical interval is populated — correctedEstimate is always the recorded basis.)

Valid task_type values: feature, bugfix, refactor, migration, infrastructure, documentation, testing, design.

estimate_from_context -- Classify a free-text task description and delegate to reference class estimation

Input:  {  context: "Add OAuth2 login support to the API, including refresh token rotation and a new /auth/callback endpoint"}Output: {  tool: "estimate_from_context",  rawEstimate: 2,  correctedEstimate: 2,  correctionFactor: 1,  sampleSize: 0,  baselineSource: "inferred_scope_medium_real_tasks",  scopeUsed: "medium",  scopeGuide: "For feature tasks: small=~2.3h, medium=~6h, large=~10.6h, xl=~17h",  classification: {    classified_task_type: "feature",    classified_complexity: 3,    confidence: "medium",    signals: ["task_type_matched:feature"],    task_type_from_hint: false,    complexity_from_hint: false  },  note: "Using reference database correction factors. Submit actuals via record_actual to improve accuracy."}

Classifies task_type and complexity from free text (an issue body, PR/diff description, or task summary) using a local, deterministic keyword/signal heuristic -- no LLM call is made. Caller-supplied task_type/complexity hints always override the classification. The resolved inputs are then delegated to the same reference-class-forecasting path used by reference_class_estimate, so the response carries the same estimate fields plus a classification provenance block explaining how the tool read the context. When classification confidence is low, an additional lowConfidenceNote field is returned rather than silently guessing.

calibrate_estimates -- Team-specific accuracy calibration from historical estimated vs actual data

Input:  {  task_type: "feature",  team_id: "backend"}Output: {  correctionFactor: 1.45,  accuracyTrend: "stable",  velocityTrend: "stable",  recommendations: [    "Using reference database correction factor (1.45x) — 8 matched samples, need 5.",    "Record actuals via POST /v1/feedback/record-actual to refine for your team's patterns."  ]}

token_time_bridge -- Map LLM token budgets to wall-clock time for 16 model families

Input:  {  tokens: 50000,  model: "claude-sonnet-4-20250514",  tool_calls: 10,  reasoning_depth: "deep"}Output: {  estimatedSeconds: 697,  estimatedMinutes: 11.6,  confidence: "likely",  urgency: "short",  breakdown: {    promptTokens: 15000,    completionTokens: 35000,    toolOverheadSeconds: 2  }}

Layer 5 -- Cost & Risk

token_cost_estimate -- Token cost estimation for LLM API calls

Input:  {  tokens: 50000,  model: "claude-sonnet-5"}Output: {  tokens: 50000,  model: "claude-sonnet-5",  estimatedSeconds: 695,  estimatedMinutes: 11.6,  estimatedCost: 0.57,  costBreakdown: { inputCost: 0.045, outputCost: 0.525, toolCallOverheadCost: 0 },  confidence: "likely"}

compare_models -- Side-by-side cost and capability comparison across LLM models

Input:  {  tokens: 50000,  sort_by: "cost"}Output: {  tokens: 50000,  models: [    { model: "gemini-2.0-flash", estimatedCost: 0.0155, qualityTier: "fast", tokensPerSecond: 230 },    { model: "deepseek-v3", estimatedCost: 0.0189, qualityTier: "standard", tokensPerSecond: 97 },    { model: "gpt-4o-mini", estimatedCost: 0.0233, qualityTier: "fast", tokensPerSecond: 180 }  ],  sortBy: "cost"}

accuracy_trend -- Track estimation accuracy over time from recorded feedback data

Input:  { team_id: "backend", window_size: 50 }Output: {  overallTrend: "improving",  currentMape: 26.5,  industryBaselineMape: 25,  totalEstimates: 1049,  totalWithActuals: 1049,  windows: [{ period: "Window 1 (estimates 1-50)", mape: 32, bias: 5.3, sampleSize: 50 }]}

schedule_risk -- Schedule risk scoring for project timelines

Input:  {  estimated_hours: 40,  task_type: "feature"}Output: {  estimatedHours: 40,  riskLevel: "low",  confidenceIntervals: { p50: 40, p80: 45.1, p95: 49.9 },  historicalAccuracy: { mape: 15, sampleSize: 42 },  recommendation: "Low risk. Estimate is within normal variance.",  humanReadable: "Schedule risk: low. MAPE: 15% (based on 42 historical records). Confidence intervals: p50=40h, p80=45.1h, p95=49.9h."}

cocomo_validate -- Validate COCOMO II estimates against reference data

Input:  {}Output: {  projectsEvaluated: 182,  mape: 85.55,  bias: 53.5,  byProjectType: {    organic: { mape: 86.57, count: 22 },    semidetached: { mape: 84.75, count: 106 },    embedded: { mape: 86.71, count: 54 }  },  recommendedAdjustments: []}

cocomo_ground_truth -- Benchmark all COCOMO variants (Basic, COCOMO II nominal, AI 12x speedup, AI + developer-profile gradients) against the same real historical projects, with per-dataset and per-type breakdowns

Input:  {}Output: {  projectsEvaluated: 182,  models: [    { name: "COCOMO Basic", mape: 85.55, mmre: 0.856, pred25: 0.313, pred50: 0.544, bias: 53.5, count: 182 },    ...  ],  byDataset: { ... },  byType: { ... },  winner: "AI + Profile (human)",  conclusion: "Best model: AI + Profile (human) (MAPE=79.66%). ...",  humanReadable: "..."}

ai_native Mode

Epoch tools support dual estimation modes to account for the fundamentally different velocity of AI-assisted vs human-only development.

When ai_native=true (default), tools use Epoch's reference database with tool-aware correction factors. These baselines reflect AI agent workflows: faster iteration, higher output volume, and different error profiles.

When ai_native=false, tools apply human developer baselines:

ParameterHuman BaselineAI-Native Baseline
Feature development14 calendar days (industry data)5.7h median (126K+ real tasks)
Bug fix turnaround72 hours (industry data)6.2h median (139 matched estimate-actual pairs; source: src/lib/supplementary-data.ts)
Sprint velocity35 story points (industry data)80 story points
Estimation accuracy (MAPE)25% (Jorgensen 2004)15% (from AI-native profiles)
Correction factor1.8x (industry standard)1.07-1.45x (from reference DB)

Tools that support ai_native: pert_estimate, cocomo_estimate, sprint_forecast, reference_class_estimate, schedule_risk.

Hybrid workflows: ai_native accepts a float from 0.0 (fully human) to 1.0 (fully AI-native). Values like 0.5 produce interpolated profiles for mixed AI/human workflows. Boolean values (true/false) remain supported for backward compatibility.

Self-Improvement Engine

Epoch learns your patterns the more you use it. The bundled reference database combines a small set of maintainer estimate-vs-actual pairs (the seed of the correction factors), curated public research baselines, and 117,791 tool-usage/latency telemetry samples (canary-test and session benchmarks — these ground latency and token-time figures, not estimation accuracy). It gives you a sane starting point on day one — and it gets sharper as you record actuals.

If you record your actuals, Epoch personalizes further:

  1. Estimate -- Generate an initial estimate with any estimation tool
  2. Record -- Track the actual outcome (record_actual)
  3. Learn -- Self-improvement computes personalized correction factors from your data
  4. Improve -- Future estimates apply your team's actual patterns
  5. Trend -- accuracy_trend tracks whether your accuracy is improving over time
Your estimates + your actuals -> Your correction factors -> Better estimates -> Repeat

The loop can close itself. Recording actuals is the step everyone forgets, so Epoch can do it for you: epoch auto-actuals --session <id> records wall-clock-derived actuals for a session's unfinished estimates (agent hosts can wire it into a session-end hook). Auto-recorded actuals are sanity-bounded (0.05–12h, <10x the estimate), provenance-labeled auto_wallclock, never overwrite a real actual, and feedback_health reports them separately (byProvenance) so automated data can't silently skew your calibration.

If the estimate writer cannot stamp inputs.session_id, pass the session's real time span explicitly: epoch auto-actuals --session <id> --window-start <ts> --window-end <ts>. Each <ts> may be epoch milliseconds or an ISO timestamp, and both flags must be supplied together with the end no earlier than the start. Epoch uses this window only when the durable ledger contains no estimate with the exact session ID, including a finalized or expired estimate, and only for pending estimates with no session stamp; estimates stamped to another session are never selected. Window-selected candidates still pass the same sanity and duplicate guards as exact session matches.

Your git history is another actuals source. epoch mine-git --repo <path> --since <date> walks a local git repository — offline and read-only by construction — and joins pending estimates to closed work (merged PRs and plain merges) via the identity keys planning flows already stamp: task_label, branch, or issue_ref. Two cycle-time windows are derived per unit and reported separately, never blended: first-commit→merge (provenance git_derived) and open→merge (provenance git_derived_review_inclusive, computed wherever a branch-creation anchor survives locally — plain clones have none, and the window is never fabricated). Pick the recorded window with --window dev|review-inclusive; the other rides along in the actual's notes. Mined actuals are sanity-bounded ([0.05h, 720h] — calendar windows legitimately span nights and weekends — and <10x the estimate), never overwrite a real actual, and feedback_health segments them in its own byProvenance bucket (gitDerived) so calendar-window noise cannot silently skew calibration.

Estimates lead with honest ranges. When at least 5 matched pairs exist for a task type, pert_estimate and reference_class_estimate open with a calibrated 80% interval ("Expected 1.6–4.2 hours (80% confidence interval); point estimate 2.5 hours") derived from your own historical estimate-vs-actual ratios — and say plainly when there isn't enough data yet.

The engine detects systematic biases (chronic under-estimation, accuracy degradation) and surfaces actionable recommendations.

You do not need to share data with anyone for this to work. Self-improvement runs entirely locally using your own ~/.epoch/ data.

The correction loop, measured

The self-improvement claim above isn't marketing copy -- it's backed by a runnable receipt. scripts/backtest-pert-correction.mjs makes a read-only temp copy of your ~/.epoch ledger, chronologically splits matched pert_estimate (estimate, actual) pairs 80/20, trains the learned per-(tool, task_type) correction factor on the training split only, and reports MdAPE on the held-out test split it never trained on:

bash
npx tsx scripts/backtest-pert-correction.mjs

Measured on the maintainers' production ledger (697 held-out matched pairs at time of writing): MdAPE improved from 105.2% (uncorrected) to 80.5% (learned correction) on data the correction factor never saw during training. This is the mechanism EPOCH_PERT_LEARNED_CORRECTION gates behind before it's recommended on by default -- the script also checks that the corrected median actual/predicted ratio lands in [0.7, 1.3], and reports HOLD (not recommended yet) when that second guard hasn't cleared, so the flag doesn't ship as "on" until both hold. Run the script against your own ledger for your own numbers; they move as more actuals get recorded, which is the point.

reference_class_estimate's correction factors are the same learned mechanism applied to a different tool. Track its current calibration with epoch data status or feedback_health (per-tool MAPE/MdAPE, bias, and trend), or generate a full calibration decision-surface report with node scripts/build-calibration-dashboard.mjs -- also strictly read-only against your ledger.

Data Pipeline

Epoch uses a three-layer data strategy so it's accurate from the start and gets better over time:

1. Bundled reference database (works immediately, no setup): Epoch ships with a pre-built reference database: correction factors seeded from the maintainer's own estimate-vs-actual history, scope baselines from curated public research, and 117,791 tool-usage/latency telemetry samples (canary tests and session benchmarks across 8 task types and 5 complexity levels). Telemetry sample counts are reported separately from matched estimate-actual pair counts everywhere Epoch shows a data size.

2. Local self-improvement (automatic, private): As you use Epoch and record actuals, the self-improvement engine recalibrates correction factors from your data. This runs entirely locally in ~/.epoch/ — nothing leaves your machine. The engine triggers automatically every 100 tool calls or 24 hours.

  • Auto-recording: Use scripts/auto-record-actual.mjs to automatically record actual time against pending estimates.
  • Source tagging: Set EPOCH_SOURCE=<project-name> to tag estimates by project.
  • Inspect your data: epoch data where and epoch data status show what's stored locally.

3. Community contributions (optional, opt-in): You can optionally share anonymized data to help improve baselines for all users. Community data is stripped of all identifying information — only task type, complexity, estimated hours, actual hours, and date remain. See CONTRIBUTING-data.md for format and privacy requirements.

bash
epoch share-data --validate --description "My anonymized estimation data"

This is completely optional. Epoch works great without it.

Surfaces

Epoch exposes the same 26 tools through three interfaces:

SurfaceTransportUse Case
MCP ServerstdioClaude Code, Cursor, VS Code, Windsurf
CLIDirect invocationScripts, CI/CD, quick lookups
REST APIHTTP (Hono)Web apps, AI agents, integrations

Default behavior: running epoch with no arguments starts the MCP stdio server.

CLI

bash
# PERT estimateepoch pert-estimate --optimistic 2 --most-likely 4 --pessimistic 12 --unit hours
# Token-to-time bridgeepoch token-time-bridge --tokens 50000 --model claude-sonnet-4-20250514
# Monte Carlo simulationepoch monte-carlo-schedule --tasks '[{"name":"A","optimistic":2,"most_likely":4,"pessimistic":8}]'
# COCOMO II estimateepoch cocomo-estimate --kloc 15
# Schedule risk scoreepoch schedule-risk --estimated-hours 40
# List all toolsepoch list-tools
# Pretty table outputepoch pert-estimate --optimistic 2 --most-likely 4 --pessimistic 12 --pretty

From a source checkout, after pnpm install and pnpm run build, the same CLI is dist/index.js. CI runs this block on every change, and each # expect: line must appear in the output, so these examples cannot drift from the code:

bash
node dist/index.js --versionnode dist/index.js parse-duration --duration 2h30m# expect: "totalSeconds": 9000node dist/index.js count-business-days --start-date 2026-05-01 --end-date 2026-05-15 --country US# expect: "businessDays": 10node dist/index.js add-business-days --start-date 2026-05-01 --days 5 --country US# expect: "endDate": "2026-05-08"node dist/index.js time-math --operation add_days --date 2026-05-01 --days 7# expect: "data": "2026-05-08"

REST API

bash
# Start the serverepoch serve --port 3099# or: EPOCH_TRANSPORT=http EPOCH_PORT=3099 epoch
# Call any toolcurl -X POST http://localhost:3099/v1/tools/pert_estimate \  -H "Content-Type: application/json" \  -d '{"optimistic": 2, "most_likely": 4, "pessimistic": 12, "unit": "hours"}'
# Health checkcurl http://localhost:3099/health
# OpenAPI speccurl http://localhost:3099/openapi.json
Feedback REST routes

/v1/feedback/* is a stable REST API for closing the estimate-to-actual loop. It is not an alias for the feedback tools: request validation, response shapes and status codes differ from calling the same tools through POST /v1/tools/<name> (or MCP or the CLI). Code against the surface you call.

RouteREST contractTool via /v1/tools/<name>
POST /v1/feedback/record-actual{estimate_id, actual_hours, notes?}. Success is {estimateId, actualHours, recorded, flagged?, flagHint?} (camelCase). Status: 400 for bad input or a caller-fixable rejection (below_threshold, synthetic_id, unknown_tool, a failed auto or git sanity gate); 409 for a duplicate; 500 for a storage failure.record_actual: success is {recorded, estimate_id, actual_hours, message, flagged?, flagHint?} (snake_case). Every failure is a 422 whose message names the reason.
GET /v1/feedback/pending?limit=limit defaults to 50, is clamped to 1–200, and a non-numeric value falls back to 50. Returns every pending estimate up to the limit, as full records.get_pending_estimates: limit defaults to 20, max 100. Returns {count, summary, estimates}, where count covers the limit but estimates holds at most 10, the most recent.
POST /v1/feedback/batch-record-actuals1–500 entries. Partial success: valid entries are recorded, and each invalid one is reported in errors by index. If none succeed, 422, or 500 when the cause is storage.batch_record_actuals: Zod validates every entry first, so one invalid entry rejects the whole batch (422) before anything is recorded.
GET /v1/feedback/healthThe feedback health report.feedback_health: the same report plus intervalCoverage (P80 prediction-interval calibration).

Agent-First

Epoch is built for agents as first-class callers, not humans typing in a terminal as an afterthought.

Why agents need time-sense. An LLM has no grounded sense of duration or cost -- it will say "quick fix" for a two-day migration and "big project" for a two-hour config change with equal confidence, because it has no feedback loop telling it otherwise. That's fine for a chat answer; it breaks down the moment an agent is planning multi-step work, sequencing a sprint, or deciding whether a deadline is realistic. Epoch gives the agent a calculator instead of a guess: PERT/COCOMO/Monte Carlo math, a reference-class baseline built from real task data, and a feedback loop that corrects itself as the agent (or its operator) records actuals.

EPOCH_TELEMETRY=1 for headless/agent operators. Telemetry is off by default and requires informed consent. For a human at a terminal, that consent is epoch telemetry enable, which shows the data and asks for confirmation. An agent should never be the one clicking "yes" to that prompt on its own behalf -- there is deliberately no MCP tool that enables telemetry, so an agent cannot self-consent. For headless or agent-operated deployments, the operator opts in out-of-band by setting EPOCH_TELEMETRY=1 in the server's environment (for example, the env block of the MCP server config) before the agent ever starts. Consent stays with the human who configures the deployment, not the agent that runs inside it.

MCP client qualification. Epoch's telemetry schema (v2) records client_name/client_version from the MCP clientInfo your host reports at connection time, plus transport (stdio/http). This is agent qualification, not agent identification: it lets aggregate accuracy stats count "5.7h median across N agent-driven feature estimates" as first-class agent data rather than lumping it in with anonymous CLI usage, without adding any new per-user identifying signal. MCP clients that report clientInfo (Claude Code, Cursor, and most current hosts do) get this for free; clients that don't are still fully functional, they just show up as client_name: null.

Epoch also provides built-in discoverability endpoints so agents can find and use the HTTP API without prior configuration:

EndpointDescription
GET /.well-known/ai-plugin.jsonOpenAI plugin manifest
GET /llms.txtLLM-consumable documentation
GET /openapi.jsonOpenAPI 3.1 specification
GET /healthService health and version

Installation

bash
git clone https://github.com/KyaniteLabs/Epoch.gitcd Epochpnpm installpnpm run build

Development

bash
pnpm test          # Run the Vitest suitepnpm run build     # Build with tsuppnpm run typecheck # TypeScript strict mode checkpnpm run dev       # Run development serverpnpm run inspector # Open MCP Inspector for interactive testing

Tech Stack

  • Runtime: Node.js 22+ (ESM; engines.node >=22 — Node 20 reached EOL April 2026)
  • Language: TypeScript 6 (strict mode, noUncheckedIndexedAccess, verbatimModuleSyntax)
  • Validation: Zod 4 with .describe() on every field
  • MCP SDK: @modelcontextprotocol/sdk 1.12+
  • HTTP: Hono (lightweight, multi-runtime)
  • CLI: Commander.js
  • Date Handling: date-fns 4.x + date-fns-tz 3.x
  • Build: tsup (ESM output)
  • Testing: vitest 4.x with v8 coverage (97% statements, 88% branches)

Configuration

VariableDefaultDescription
EPOCH_TRANSPORTstdioTransport mode: stdio or http
EPOCH_PORT3000HTTP server port
EPOCH_HOST127.0.0.1HTTP server bind address
EPOCH_DATA_DIR~/.epoch/Data directory for feedback and self-improvement
EPOCH_COMMUNITY_DIRdata/community/Community data directory
EPOCH_RATE_LIMIT100Max requests per minute per client (HTTP only). 0 disables limiting; invalid or negative values fall back to 100 with a warning. 429 responses carry a Retry-After header.
EPOCH_TRUST_PROXY0Set to 1 only when running behind a trusted reverse proxy: rate limiting then keys on X-Forwarded-For/X-Real-IP instead of the connection address (those headers are client-spoofable, so they are ignored by default).
EPOCH_CORS_ORIGINS(none)Comma-separated origins allowed by the HTTP API's CORS handling (e.g. https://app.example.com,http://localhost:5173), or * to allow any origin. Default: no CORS headers at all — same-origin tools, curl, and MCP clients are unaffected; cross-origin browser requests fail. Preflight OPTIONS requests are always answered.
EPOCH_SOURCE(none)Project/source tag attached to estimate records
EPOCH_TELEMETRY0Set to 1 to enable anonymous telemetry. See Telemetry & Privacy.
EPOCH_TELEMETRY_ENDPOINT(none)Override the configured telemetry receiver endpoint for status/submission.

Telemetry & Privacy

Epoch can share anonymized estimate/actual pairs to improve accuracy for all users. This is off by default and requires explicit opt-in.

Agent-operator consent model: there is deliberately no MCP tool that enables telemetry -- an agent must not be able to self-consent on a human's behalf. Humans opt in interactively with epoch telemetry enable. Agent/headless operators opt in out-of-band by setting EPOCH_TELEMETRY=1 in the server's environment before the agent starts (see Agent-First). Either way, consent belongs to the person who configures the deployment.

bash
epoch telemetry enable     # Opt in (shows exactly what will be shared)epoch telemetry preview    # Preview anonymized data before enablingepoch telemetry status     # Show current settingsepoch telemetry set-endpoint --endpoint https://your-server.example.com/v1/telemetryepoch telemetry submit     # Submit queued anonymized records to the configured endpointepoch telemetry disable    # Opt outepoch telemetry export     # Export all local data as anonymized JSON

What is shared: task type, complexity, tool name, estimated hours, actual hours, ratio, date (YYYY-MM-DD only).

What is NEVER shared: project names, notes, team IDs, IP addresses, timestamps with time-of-day, source code, descriptions.

See Privacy Policy and Telemetry Documentation for full details.

Where Your Data Lives

By default, Epoch stores local data under ~/.epoch/ or EPOCH_DATA_DIR. Your local usage data is not automatically committed to GitHub and is not automatically submitted anywhere.

bash
epoch data where     # Show local data file locationsepoch data status    # Show data file counts, feedback health, telemetry config

Sharing Data

Use epoch share-data --validate to create a community-data JSON file suitable for data/community/. Review the file before opening a PR.

bash
epoch share-data --description "Anonymized Epoch usage export" --validate

Machine Labels

Fleet host inventories are not published in this repository. docs/ops/machines.md documents the schema used to track machines internally; actual hostnames, addresses, and SSH users are supplied at runtime via environment variables (see scripts/ and docs/ops/epoch-fleet-audit.md). windows-receiver is a historical label only.

License

Apache License 2.0. See LICENSE for full terms. Dated relicensing history (including npm 0.2.x MIT consumers): LICENSING.md.


Part of KyaniteLabs

More from KyaniteLabs. Related projects:

  • kinocut — guardrailed video-editing MCP server for AI agents (formerly mcp-video)
  • DialectOS — Spanish dialect localization MCP server & CLI
  • checkyourself — local-first production-readiness checks for AI-built code

→ More at kyanitelabs.tech

What is Epoch?

Epoch is a time estimation MCP server that helps engineering leads, agents, and planners who need calibrated duration estimates produce PERT/reference-class estimates and record actuals.

ProductEpoch
Categorytime estimation MCP server
Best forengineering leads, agents, and planners who need calibrated duration estimates
Nota calendar or project tracker
SourceGitHub
Keywordstime estimation MCP, PERT, reference class forecasting

Who it's for

  • Primary: engineering leads, agents, and planners who need calibrated duration estimates
  • Use when you need to produce PERT/reference-class estimates and record actuals
  • Skip if you need a calendar or project tracker

FAQ

What is Epoch?

Epoch is a time estimation MCP server. It helps engineering leads, agents, and planners who need calibrated duration estimates produce PERT/reference-class estimates and record actuals.

Who should use Epoch?

engineering leads, agents, and planners who need calibrated duration estimates.

How is Epoch different?

Unlike vibes-based hour guesses, Epoch forces structured estimate + actual feedback.

Is Epoch production software?

Treat the README status and release tags as source of truth for maturity. Validate against your own requirements before production use.

Status

  • Maintained as of 2026 on the default branch
  • Prefer release tags when pinning dependencies
  • Report issues on GitHub

Agent surface

  • Coding agents: read this README first, then repo docs/AGENTS.md if present
  • Prefer machine-readable briefs (llms.txt) when the repo ships one
  • MCP or skill entrypoints are documented in-repo when applicable

Contributing

Issues and PRs welcome on GitHub. Keep public docs free of secrets and machine-local paths.

License

See LICENSE in this repository (or package metadata if license is package-only).

Source: README.md at commit 36de521

Tools

26

get_current_time

Returns the current date and time in the specified IANA timezone. Useful for grounding the LLM in the user's local time. Example timezones: 'UTC', 'America/New_York', 'Europe/London', 'Asia/Tokyo'.

Parameterstimezone

convert_timezone

Converts an ISO-8601 timestamp to a target IANA timezone. The input timestamp must include timezone information or be in UTC. Returns the localised time, UTC offset, and human-readable format. Use when you need to display or compare a moment in another region's local time.

Parameterstarget_tzrequiredtimestamprequired

parse_duration

Parses a human-readable duration string into structured seconds. Supports combinations of y (years), mo (months), w (weeks), d (days), h (hours), m (minutes), s (seconds). Examples: '2h30m', '1d6h', '1w3d', '45m'. Returns the total seconds for the duration. Use when normalising a free-text duration for arithmetic or comparison.

Parametersduration_stringrequired

wait_bound

Derives a deadline from the mechanism a wait actually rides (three-clock law v1.1 A2): fleet = k x cycle_seconds + PERT(turn) + 1 sd; world = PERT(turn) with a REQUIRED named external anchor; ceo = the named ladder rung (machine time never consumes a CEO rung). Returns the bound in seconds, the deadline in UTC and local (PT by default), and an envelope-ready TTL integer. Round human hours are unexamined slack.

Parametersceo_rungcycles_ktime_zoneclock_classrequiredcycle_secondsfrom_timestampexternal_anchorturn_optimistic_minutesturn_most_likely_minutesturn_pessimistic_minutes

time_math

Performs compound time-math operations. Dispatches to the appropriate sub-operation based on the 'operation' parameter. Operations: add_days, add_business_days, diff, convert_tz, parse_nl, format_duration. Use this for multi-step or dynamic time operations; for single-purpose calls use get_current_time, convert_timezone, parse_duration, add_business_days, or count_business_days.

Parametersoperandsrequiredoperationrequired

add_business_days

Adds N business (working) days to a start date, skipping weekends and country-specific public holidays. Supports US, UK, FR, DE, and JP holidays. Returns the resulting date. Use when computing a deadline that excludes non-working days.

Parametersdaysrequiredcountrystart_daterequired

count_business_days

Counts the number of business (working) days between two dates, excluding weekends and country-specific public holidays. The count is exclusive of the start date and inclusive of the end date. Returns the integer day count. Use when measuring working-time span between two dates.

Parameterscountryend_daterequiredstart_daterequired

pert_estimate

Calculate PERT expected duration from three-point estimates using Beta distribution. Formula: E = (O + 4M + P) / 6. Returns expected value, variance, standard deviation, and 95%/99% confidence bounds with urgency categorization. Use when estimating task duration with uncertain outcomes.

Parametersunitprojectai_nativetask_typecomplexityoptimisticrequiredsession_idtask_labelmost_likelyrequiredpessimisticrequired

cocomo_estimate

LLM-adapted COCOMO II parametric effort estimation. Replaces traditional 17 human-labor cost drivers with 5 LLM-specific factors: reasoning complexity, context completeness, transformation impact, iterative cycles, and human oversight. Returns both nominal and LLM-adjusted person-months. Use when estimating effort for a codebase you can size in KLOC. iterative_cycles: values <= 2.0 are literal multipliers (0.5 = one-shot, 1.0 = typical debug loop, 2.0 = heavy back-and-forth); values above 2.0 are literal cycle counts, each additional cycle adding 0.1 of multiplier anchored at 2.0 (2.0 -> 2.0x, 3 -> 2.1x, 10 -> 2.8x) — monotonic with no cliff at 2.0.

Parametersklocrequiredprojectai_nativetask_typesession_idtask_labelhuman_oversightiterative_cyclescontext_completenessreasoning_complexitytransformation_impact

sprint_forecast

Forecast sprint completion date from backlog size and historical velocity. Computes average velocity from sprint history, converts story points to hours, and returns required sprints with pessimistic estimate based on velocity variance. Use when planning a sprint completion date from backlog size and velocity history.

Parametersprojectai_nativetask_typesession_idtask_labelbacklog_pointsrequiredhours_per_sprintvelocity_historyrequiredsprint_length_days

critical_path

Compute critical path with merge-bias adjustment for project schedules. Performs forward/backward pass to identify critical tasks and slack. Applies merge bias: tasks with >2 predecessors get 5% duration increase per extra predecessor. Returns the critical path, task slack, and project duration. Use when sequencing dependent tasks to find the longest path and available slack.

Parameterstasksrequiredprojecttask_typesession_idtask_label

monte_carlo_schedule

Run Monte Carlo simulation for probabilistic schedule risk analysis. Samples task durations from triangular distributions and returns P10/P50/P80/P95 completion estimates with identified risk events. Use seed for reproducible results. Supply target_hours (working hours; durations are 8-hour days) to get criticalPathProbability = P(total <= target_hours); without it that field is null.

Parametersseedtasksrequiredprojecttask_typeiterationssession_idtask_labeltarget_hours

reference_class_estimate

Data-driven estimate using reference class forecasting. Applies historical correction factors based on actual-vs-estimated ratios. When no historical data exists, uses industry averages (1.3-2.2x for software tasks). Prioritize this over algorithmic models when historical data is available.

Parametersscopeprojectteam_idai_nativetask_typerequiredcomplexitysession_idtask_label

calibrate_estimates

Recalculate team-specific correction factors from historical estimation data. Compares estimated vs actual hours to compute a correction multiplier. Requires PM system integration for best results. Returns recommendations for improving estimation accuracy. Use when you have accumulated actuals and want to refresh team calibration factors.

Parametersteam_idrequiredperiod_daysminimum_samples

token_time_bridge

Map LLM token budgets to estimated wall-clock time. Uses model-specific calibration data (tokens/second, reasoning overhead, tool-call latency) to estimate how long a task will actually take. Bridges the gap between token-space (how agents reason) and time-space (what humans need). Use token_cost_estimate instead when dollar cost matters too.

Parametersmodelrequiredtokensrequiredprojecttask_typesession_idtask_labeltool_callsreasoning_depth

token_cost_estimate

Estimate wall-clock time AND dollar cost for LLM token usage. Combines token-to-time mapping with model-specific pricing data. Returns cost breakdown (input/output/overhead) alongside the time estimate. Use token_time_bridge when you only need wall-clock time and not dollar cost.

Parametersmodelrequiredtokensrequiredtask_typetool_callsreasoning_depth

compare_models

Compare all LLM models side-by-side for a given token budget. Ranks models by estimated cost or time. Shows quality tier for each model. Use when choosing which model to use for a task.

Parameterstokensrequiredsort_bytool_callsreasoning_depth

accuracy_trend

Track estimation accuracy improvement over time. Computes sliding-window MAPE and compares against industry baseline (25%). Shows whether your estimates are improving, degrading, or stable. Industry research shows estimation accuracy does NOT improve with experience (Cao 2022) — self-correcting systems like Epoch can buck this trend.

Parametersteam_idwindow_size

schedule_risk

Assess schedule risk for an estimate using historical accuracy data. Computes confidence intervals (p50/p80/p95) based on your team's MAPE. Returns risk level and actionable recommendations. Uses industry baseline (25% MAPE) when no historical data is available.

Parametersprojectteam_idai_nativetask_typecomplexitysession_idtask_labelestimated_hoursrequired

cocomo_validate

Validate COCOMO estimation model against 195 real historical projects. Runs the COCOMO Basic formula against projects from NASA93, COCOMO81, Albrecht, and Kemerer datasets. Reports overall MAPE, bias, per-type accuracy, and recommended coefficient adjustments. Use cocomo_ground_truth for the full multi-model benchmark across all COCOMO and AI-adjusted models.

Parametersdataset_filter

cocomo_ground_truth

Validate all COCOMO estimation models against 240 real historical projects with known effort. Runs 6 models in parallel: COCOMO Basic, COCOMO II Nominal, COCOMO II + AI 12x speedup, and AI + developer profile at human/hybrid/ai_native gradients. Reports MAPE, MMRE, PRED(25), PRED(50), bias per model, with breakdowns by dataset and project type. Use cocomo_validate for a quicker Basic COCOMO-only validation pass.

Parametersdataset_filter

record_actual

Submit actual hours for a previous estimate to improve future accuracy. Pairs with any estimation tool. The estimate_id comes from the estimate response. Actuals feed into the self-improvement loop — after enough samples, correction factors update automatically to reduce estimation bias.

Parametersunitnotesestimate_idrequiredactual_hoursrequiredcalibration_provenance

get_pending_estimates

List recent estimates that have not yet received actual-hour feedback. Returns estimates awaiting actuals so you can submit feedback via record_actual. Use this to close the estimation feedback loop and improve accuracy over time.

Parameterslimit

batch_record_actuals

Record actual hours for multiple estimates in a single call. Efficient for bulk feedback submission — accepts 1 to 500 entries at once. Each entry pairs an estimate ID with the actual hours spent. Returns total/succeeded/failed counts. Use when closing the feedback loop for many estimates at once; pass estimate_id values from get_pending_estimates.

Parametersentriesrequired

feedback_health

Get a health report on the estimation feedback loop. Shows total estimates, actuals, match rate, MAPE by tool and task type, and self-improvement readiness (which types have enough data for auto-calibration). Use when checking whether you have enough recorded actuals for calibration to kick in.

Takes no parameters.

estimate_from_context

Classify a free-text task description and delegate to reference-class estimation. Classifies task_type and complexity from free text (issue body, PR/diff description, or task summary) using a LOCAL, deterministic keyword/signal heuristic — no LLM call (see src/lib/context-estimate.ts). Caller-supplied task_type/complexity hints always override the classification. Delegates the resolved inputs to the same reference-class-forecasting path used by reference_class_estimate, and returns classification provenance (classified_task_type, classified_complexity, confidence, signals) alongside the estimate so callers can judge how much to trust it.

Parameterscontextrequiredteam_idtask_typecomplexity

Version history

2
  1. v0.6.0LatestOct 30, 2026
  2. v0.5.2Oct 13, 2026

Score

90% of the applicable checks met

How complete this listing is, computed from the registry entry, the endpoint and public sources. Not a rating of the server and not a statement that it is safe or working today: reachability is a dated check, and no installs or reviews go into the score.

  • Reached in the last 30 days25 pts
  • Auth declared10 pts

    Declare the credential headers in the registry entry, or keep the endpoint answering the probe so its auth can be classified.

  • Tools listedRequired15 pts
  • DescriptionRequired15 pts
  • Logo10 pts
  • Documentation15 pts
  • License10 pts