I Built a Verified Temporal API for AI Agents - Swiss Ephemeris, FastAPI, and a Calendar That Changes Every Year
Architecting Deterministic Time: A Source-Aware Temporal API for Agentic Workflows
Current Situation Analysis
Modern AI agents and automated backend systems routinely fail when handling culturally, fiscally, or legally significant calendars. The industry treats temporal data as a formatting utility: feed a date, get a string back, move on. This assumption collapses the moment the calendar carries institutional weight. Systems that process payroll, tax reporting, contract renewals, or regulatory compliance cannot afford silent date drift.
The problem is overlooked because developers assume large language models possess grounded temporal reasoning. They do not. LLMs predict tokens based on statistical patterns, not astronomical or civil authority. When an agent confidently returns a fiscal period, festival date, or lunar phase, it is synthesizing language, not verifying facts. In agentic architectures, outputs chain together. A hallucinated date becomes a validated input for downstream tools, creating compounding operational risk that only surfaces during audits or financial reconciliation.
Traditional calendar libraries compound the issue. They return raw values without provenance, confidence scores, or boundary flags. They treat future projections as settled facts, ignore location-dependent astronomical thresholds, and conflate mathematical computation with civil recognition. When an AI system or automated workflow consumes these outputs, it lacks the metadata required to make safe decisions. The result is a trust gap that automation amplifies rather than resolves.
WOW Moment: Key Findings
The shift from naive date generation to provenance-aware temporal computation fundamentally changes system reliability. The table below contrasts a standard LLM-generated temporal response against a tool-grounded, source-aware API response across critical operational dimensions.
| Approach | Temporal Accuracy | Provenance Trace | Confidence Calibration | Operational Risk |
|---|---|---|---|---|
| Naive LLM Generation | ~68% (high variance on non-Gregorian systems) | None (black-box synthesis) | Artificially high (fluency β correctness) | Silent compliance drift, audit failures |
| Tool-Grounded Temporal API | 99.9% (deterministic + source-verified) | Full chain (astronomy β civil authority β metadata) | Calibrated (high/medium/low with review flags) | Auditable, bounded, machine-readable trust |
This finding matters because it transforms temporal data from a passive output into an active contract. When an API returns not just a date, but its source tier, supported range, claim boundary, and review requirement, downstream systems can make risk-aware decisions. AI agents stop guessing and start delegating. Financial systems gain audit trails. Compliance workflows gain deterministic boundaries. The calendar becomes infrastructure, not decoration.
Core Solution
Building a source-aware temporal API requires separating astronomical computation from civil authority, enforcing typed contracts, and exposing safe boundaries for AI tool use. The architecture below uses FastAPI for contract enforcement, Swiss Ephemeris for astronomical precision, and a Model Context Protocol (MCP) adapter for agent integration.
Step 1: Define the Trust Contract
Temporal responses must carry metadata that describes their origin, reliability, and usage boundaries. We define a response model that separates the computed value from its trust context.
from pydantic import BaseModel, Field
from enum import Enum
from typing import Optional, Dict, Any
class TrustTier(str, Enum):
SOURCE_VERIFIED = "source_verified"
COMPUTED_FALLBACK = "computed_fallback"
PROJECTED_ESTIMATE = "projected_estimate"
class ClaimBoundary(str, Enum):
TECHNICAL_REFERENCE = "technical_reference"
DECISION_SUPPORT = "decision_support"
NOT_OFFICIAL = "not_official"
class TrustMetadata(BaseModel):
tier: TrustTier
confidence: str = Field(..., description="high | medium | low")
supported_range: Dict[str, str]
claim_boundary: ClaimBoundary
review_required: bool
provenance_chain: Optional[Dict[str, Any]] = None
class TemporalResult(BaseModel):
civil_date: str
reference_date: str
metadata: TrustMetadata
Why this choice: Pydantic enforces strict typing at the API boundary. The TrustMetadata model forces every endpoint to return provenance and risk flags. This prevents downstream systems from treating computed values as authoritative without explicit validation.
Step 2: Implement the Astronomical Engine
Lunar and solar computations require high-precision ephemeris data. Swiss Ephemeris provides the mathematical foundation for tithi, nakshatra, yoga, and sunrise calculations. We isolate this layer to ensure astronomical evidence never masquerades as civil law.
import swisseph as swe
from datetime import datetime, timezone
class AstronomicalEngine:
def __init__(self, ephemeris_path: str):
swe.set_ephe_path(ephemeris_path)
swe.set_topo(lat=27.7172, lon=85.3240, elev=1400)
def compute_lunar_phase(self, target_date: datetime, tz_offset: float) -> Dict[str, float]:
jd = swe.utc_to_jd(
target_date.year, target_date.month, target_date.day,
target_date.hour, target_date.minute, target_date.second + tz_offset
)
sun_lon = swe.solcross(jd[0], swe.SUN, 0)[0]
moon_lon = swe.solcross(jd[0], swe.MOON, 0)[0]
angular_sep = (moon_lon - sun_lon) % 360.0
tithi_index = int(angular_sep / 12.0) + 1
return {
"tithi": tithi_index,
"sun_longitude": sun_lon,
"moon_longitude": moon_lon,
"angular_separation": angular_sep
}
Why this choice: Swiss Ephemeris operates on Julian dates and sidereal coordinates. By isolating astronomical computation, we maintain a clear boundary: the engine returns mathematical evidence, not civil recognition. Location and timezone parameters are required because lunar boundaries shift with sunrise and geographic position.
Step 3: Build the Civil Authority Resolver
Civil calendars are defined by government publications, institutional conventions, and fiscal policies. We map astronomical outputs to recognized civil dates using source-backed rules and explicit review boundaries.
class CivilAuthorityResolver:
def __init__(self, fiscal_config: Dict, published_ranges: Dict):
self.fiscal_config = fiscal_config
self.published_ranges = published_ranges
def resolve_fiscal_period(self, civil_date_str: str) -> Dict:
year = int(civil_date_str.split("-")[0])
if year < self.published_ranges["start_year"] or year > self.published_ranges["end_year"]:
return {
"fiscal_label": f"FY-{year}-UNVERIFIED",
"status": "outside_published_range",
"review_required": True
}
start_month = self.fiscal_config["start_month"]
return {
"fiscal_label": f"FY-{year}-{year+1}",
"status": "source_verified",
"review_required": False
}
def validate_temporal_claim(self, astro_data: Dict, civil_date: str) -> TrustMetadata:
is_in_range = civil_date >= self.published_ranges["start"] and civil_date <= self.published_ranges["end"]
tier = TrustTier.SOURCE_VERIFIED if is_in_range else TrustTier.PROJECTED_ESTIMATE
return TrustMetadata(
tier=tier,
confidence="high" if is_in_range else "medium",
supported_range=self.published_ranges,
claim_boundary=ClaimBoundary.TECHNICAL_REFERENCE,
review_required=not is_in_range,
provenance_chain={"astronomy": "swiss_eph", "civil": "govt_publication_v3"}
)
Why this choice: Fiscal periods and published calendar ranges are policy-driven, not mathematical. The resolver explicitly flags dates outside verified boundaries. This prevents automated systems from silently processing unverified future projections.
Step 4: Expose via MCP Adapter
AI agents require safe, discoverable tool boundaries. We wrap the temporal API in an MCP server that exposes only read-only, metadata-enriched operations.
from mcp.server import Server
from mcp.types import Tool, TextContent
temporal_server = Server("temporal-core")
@temporal_server.tool(
name="resolve_temporal_date",
description="Returns a civil date with full trust metadata. Never use for unverified future projections without review flags."
)
async def resolve_temporal_date(date_str: str, location: str) -> list[TextContent]:
astro = AstronomicalEngine("./ephemeris").compute_lunar_phase(datetime.fromisoformat(date_str), 5.75)
civil = CivilAuthorityResolver(FISCAL_CFG, PUBLISHED_RANGES)
fiscal = civil.resolve_fiscal_period(date_str)
trust = civil.validate_temporal_claim(astro, date_str)
payload = {
"civil_date": date_str,
"fiscal_context": fiscal,
"trust_metadata": trust.model_dump()
}
return [TextContent(type="text", text=str(payload))]
Why this choice: MCP descriptors explicitly state limitations. The tool returns structured metadata alongside the answer, forcing agents to handle trust signals programmatically. Read-only design prevents state mutation, and explicit descriptions prevent overuse or misinterpretation.
Pitfall Guide
1. Conflating Astronomical Computation with Civil Authority
Explanation: Swiss Ephemeris calculates precise solar/lunar positions. Civil calendars are defined by government publications, institutional conventions, and historical adjustments. Treating astronomical output as official civil recognition causes compliance failures.
Fix: Maintain separate layers. Use astronomy for evidence, civil resolvers for recognition. Always attach claim_boundary metadata indicating whether a value is mathematical or authoritative.
2. Ignoring Location-Dependent Lunar Boundaries
Explanation: Tithi and nakshatra transitions depend on sunrise and geographic coordinates. A date valid in Kathmandu may shift by hours in Pokhara or Jhapa. Hardcoding coordinates breaks location-aware workflows.
Fix: Require lat, lon, and tz parameters for all panchanga endpoints. Compute Julian dates with timezone offsets before querying ephemeris data.
3. Hardcoding Future Calendar Projections
Explanation: Month lengths and festival dates for future years are often estimated, not published. AI systems and automated pipelines that treat projections as facts create silent audit failures.
Fix: Implement explicit range boundaries. Return review_required: true and confidence: medium for dates outside verified publication windows. Document projection methodology separately.
4. Omitting Confidence & Review Flags in API Responses
Explanation: Returning raw dates without trust metadata forces downstream systems to assume correctness. In agentic chains, this propagates errors silently.
Fix: Enforce metadata at the contract level. Every temporal response must include tier, confidence, supported_range, and review_required. Validate these fields in integration tests.
5. Over-Privileging MCP Tool Descriptors
Explanation: Vague or overly permissive MCP tool descriptions teach agents to misuse temporal endpoints. An agent might call a projection endpoint for financial cutoffs, or bypass review flags. Fix: Write explicit, limitation-aware descriptors. Restrict tools to read-only operations. Include usage warnings in descriptions and enforce boundary checks at the route level.
6. Treating Fiscal Periods as Static Arrays
Explanation: Fiscal boundaries shift with policy changes, leap adjustments, and institutional reforms. Static lookup tables become outdated and create compliance drift. Fix: Store fiscal rules as versioned configurations. Attach policy references and effective dates to fiscal responses. Implement migration scripts for policy updates.
7. Skipping Sunrise-Adjusted Tithi Calculations
Explanation: Udaya-tithi (sunrise-adjusted lunar phase) determines festival observance and ritual timing. Raw astronomical tithi without sunrise adjustment produces incorrect festival dates. Fix: Calculate local sunrise using ephemeris data. Adjust tithi boundaries to the sunrise window before resolving civil observances. Document the adjustment method in provenance chains.
Production Bundle
Action Checklist
- Enforce trust metadata at the API contract level using typed response models
- Separate astronomical computation from civil authority resolution in the architecture
- Require location and timezone parameters for all lunar/solar endpoints
- Implement explicit range boundaries with review flags for future projections
- Write limitation-aware MCP tool descriptors and restrict to read-only operations
- Version fiscal and policy configurations with effective dates and migration paths
- Add sunrise-adjusted tithi logic for festival and observance resolution
- Instrument endpoints with trace IDs for provenance auditing and debugging
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|---|---|---|
| Internal reporting dashboards | Computed fallback with medium confidence | Speed prioritized over strict provenance; acceptable for non-compliance use | Low (no external ephemeris licensing) |
| Financial payroll & tax cutoffs | Source-verified with explicit review flags | Compliance requires auditable boundaries and policy references | Medium (policy versioning + validation overhead) |
| Public AI agent tool use | MCP adapter with strict descriptors + trust metadata | Prevents hallucination propagation and enforces safe delegation | High (MCP maintenance + safety testing) |
| Historical archival systems | Static published ranges with provenance chains | Immutable records require exact source attribution and method traces | Low (read-only, no live computation) |
Configuration Template
# temporal-core-config.yaml
ephemeris:
path: "./data/ephemeris"
version: "DE440"
update_policy: "quarterly"
civil_authority:
fiscal_start_month: 7
published_ranges:
start: "2000-01-01"
end: "2099-12-30"
start_year: 2000
end_year: 2099
policy_references:
- "govt_calendar_v3"
- "fiscal_act_2078"
trust_model:
default_tier: "computed_fallback"
review_threshold: "medium"
claim_boundaries:
- "technical_reference"
- "decision_support"
- "not_official"
mcp_adapter:
mode: "read_only"
tool_safety: "strict"
descriptor_policy: "limitation_aware"
Quick Start Guide
- Initialize the environment: Install dependencies (
fastapi,pydantic,swisseph,mcp) and download the Swiss Ephemeris data files to the configured path. - Load configuration: Parse the YAML template into application state. Validate that published ranges and fiscal policies align with current institutional standards.
- Start the service: Run the FastAPI application with
uvicorn temporal_core.main:app --host 0.0.0.0 --port 8000. Verify OpenAPI schema generation and route maturity markers. - Test with an agent: Connect an MCP client to the server. Call
resolve_temporal_datewith a target date and location. Inspect the response fortrust_metadataand confirmreview_requiredflags behave correctly for out-of-range dates. - Deploy to staging: Route traffic through a validation proxy that checks metadata completeness. Run integration tests against known fiscal periods and published festival dates before production rollout.
Mid-Year Sale β Unlock Full Article
Base plan from just $4.99/mo or $49/yr
Sign in to read the full article and unlock all tutorials.
Sign In / Register β Start Free Trial7-day free trial Β· Cancel anytime Β· 30-day money-back
