Skip to content

Schema

All data models are defined as Pydantic v2 BaseModel subclasses in src/ctx/core/schema.py. The current schema version is 0.4.0, stored in ctx.core.migrations.SCHEMA_VERSION.


Manifest Layer

The manifest is the root configuration object for a context store, persisted as .context-teleport/manifest.json.

Manifest

class Manifest(BaseModel):
    schema_version: str = SCHEMA_VERSION
    project: ProjectInfo
    adapters: dict[str, AdapterConfig] = {"claude_code": AdapterConfig(enabled=True)}
    team: dict[str, list[TeamMember]] = {"members": []}
    created_at: datetime
    updated_at: datetime
Field Type Default Description
schema_version str "0.4.0" Schema version for migration support
project ProjectInfo required Project identification
adapters dict[str, AdapterConfig] {"claude_code": ...} Registered adapter configurations
team dict[str, list[TeamMember]] {"members": []} Team member registry
created_at datetime now (UTC) Store creation timestamp
updated_at datetime now (UTC) Last modification timestamp

ProjectInfo

class ProjectInfo(BaseModel):
    name: str
    id: str = Field(default_factory=uuid4)
    repo_url: str = ""
Field Type Default Description
name str required Human-readable project name
id str UUID v4 Unique project identifier
repo_url str "" Git remote URL

AdapterConfig

class AdapterConfig(BaseModel):
    enabled: bool = True
Field Type Default Description
enabled bool True Whether the adapter is active

TeamMember

class TeamMember(BaseModel):
    name: str
    machine: str
    added: datetime = Field(default_factory=now)
Field Type Default Description
name str required Team member name
machine str required Machine hostname
added datetime now (UTC) When the member was registered

Knowledge

Knowledge entries are stored as markdown files under .context-teleport/knowledge/. Each file corresponds to one KnowledgeEntry.

KnowledgeEntry

class KnowledgeEntry(BaseModel):
    key: str
    content: str
    updated_at: datetime = Field(default_factory=now)
    author: str = ""
    agent: str = ""
Field Type Default Description
key str required Entry identifier (becomes filename: <key>.md)
content str required Markdown content
updated_at datetime now (UTC) Last update timestamp
author str "" Human author name
agent str "" Agent that wrote the entry (v0.3.0+)

Conventions

Conventions are stored as markdown files under .context-teleport/conventions/. Each file corresponds to one ConventionEntry.

ConventionEntry

class ConventionEntry(BaseModel):
    key: str
    content: str
    updated_at: datetime = Field(default_factory=now)
    author: str = ""
Field Type Default Description
key str required Entry identifier (becomes filename: <key>.md)
content str required Markdown content
updated_at datetime now (UTC) Last update timestamp
author str "" Agent or user that wrote the entry

Skills

Skills are stored as directories under .context-teleport/skills/<name>/SKILL.md. Each SKILL.md file contains YAML frontmatter and a markdown body.

SkillEntry

class SkillEntry(BaseModel):
    name: str
    description: str
    content: str  # complete SKILL.md (frontmatter + body)
    updated_at: datetime = Field(default_factory=now)
    agent: str = ""
Field Type Default Description
name str required Skill name (directory name)
description str required Short description (from frontmatter)
content str required Full SKILL.md content including YAML frontmatter
updated_at datetime now (UTC) Last update timestamp
agent str "" Agent that wrote the skill

Decisions (ADR)

Decision records follow the Architecture Decision Record pattern. Stored as numbered markdown files under .context-teleport/knowledge/decisions/.

DecisionStatus

class DecisionStatus(str, Enum):
    proposed = "proposed"
    accepted = "accepted"
    deprecated = "deprecated"
    superseded = "superseded"

Decision

class Decision(BaseModel):
    id: int
    title: str
    status: DecisionStatus = DecisionStatus.accepted
    date: datetime = Field(default_factory=now)
    author: str = ""
    context: str = ""
    decision: str = ""
    consequences: str = ""
Field Type Default Description
id int required Sequential decision number
title str required Decision title
status DecisionStatus accepted Current lifecycle status
date datetime now (UTC) Date the decision was made
author str "" Decision author
context str "" Why this decision was needed
decision str "" What was decided
consequences str "" Expected consequences

Properties:

Property Returns Description
slug str URL-safe slug from title (lowercase, max 60 chars)
filename str File name: <id:04d>-<slug>.md (e.g. 0001-use-postgres.md)

Methods:

Method Description
to_markdown() -> str Serialize to ADR markdown format
from_markdown(text, id?) -> Decision Parse from ADR markdown (class method)

Session State

Ephemeral state tracking for the current working session. Stored as .context-teleport/state.json.

ActiveState

class ActiveState(BaseModel):
    current_task: str = ""
    blockers: list[str] = Field(default_factory=list)
    progress: dict[str, Any] = Field(default_factory=dict)
    last_agent: str = ""
    last_machine: str = ""
    updated_at: datetime = Field(default_factory=now)
Field Type Default Description
current_task str "" Description of current task
blockers list[str] [] Active blockers
progress dict[str, Any] {} Arbitrary progress key-value pairs
last_agent str "" Last agent that modified state
last_machine str "" Last machine hostname
updated_at datetime now (UTC) Last update timestamp

RoadmapItem

class RoadmapItem(BaseModel):
    title: str
    status: str = "planned"
    milestone: str = ""
Field Type Default Description
title str required Item title
status str "planned" Status string (free-form)
milestone str "" Associated milestone

Roadmap

class Roadmap(BaseModel):
    items: list[RoadmapItem] = Field(default_factory=list)
    updated_at: datetime = Field(default_factory=now)
Field Type Default Description
items list[RoadmapItem] [] List of roadmap items
updated_at datetime now (UTC) Last update timestamp

Preferences

TeamPreferences

class TeamPreferences(BaseModel):
    values: dict[str, Any] = Field(default_factory=dict)
Field Type Default Description
values dict[str, Any] {} Arbitrary team-wide preference key-value pairs

UserPreferences

class UserPreferences(BaseModel):
    values: dict[str, Any] = Field(default_factory=dict)
Field Type Default Description
values dict[str, Any] {} Arbitrary per-user preference key-value pairs

Session History

Session summaries record what each agent session accomplished. Stored as NDJSON in .context-teleport/sessions.ndjson.

SessionSummary

class SessionSummary(BaseModel):
    id: str = Field(default_factory=uuid4)
    agent: str = ""
    user: str = ""
    machine: str = ""
    started: datetime = Field(default_factory=now)
    ended: datetime | None = None
    summary: str = ""
    knowledge_added: list[str] = Field(default_factory=list)
    decisions_added: list[str] = Field(default_factory=list)
    skills_used: list[str] = Field(default_factory=list)
Field Type Default Description
id str UUID v4 Session identifier
agent str "" Agent name (e.g. claude-code, cursor)
user str "" User name
machine str "" Machine hostname
started datetime now (UTC) Session start time
ended datetime \| None None Session end time
summary str "" Free-text session summary
knowledge_added list[str] [] Keys of knowledge entries written
decisions_added list[str] [] IDs/titles of decisions created
skills_used list[str] [] Names of skills used during session (v0.4.0+)

Skill Tracking (v0.4.0)

Skill tracking data is stored as sidecar files alongside each skill directory:

  • .context-teleport/skills/<name>/.usage.ndjson -- append-only usage events
  • .context-teleport/skills/<name>/.feedback.ndjson -- append-only feedback entries
  • .context-teleport/skills/<name>/.proposals/<uuid>.json -- improvement proposals

All sidecar files are created lazily on first write and synced via git.

SkillUsageEvent

class SkillUsageEvent(BaseModel):
    id: str = Field(default_factory=uuid4)
    session_id: str = ""
    agent: str = ""
    timestamp: datetime = Field(default_factory=now)
Field Type Default Description
id str UUID v4 Event identifier
session_id str "" Session that used the skill
agent str "" Agent that used the skill
timestamp datetime now (UTC) When the skill was used

SkillFeedback

class SkillFeedback(BaseModel):
    id: str = Field(default_factory=uuid4)
    agent: str = ""
    rating: int = 3
    comment: str = ""
    timestamp: datetime = Field(default_factory=now)
Field Type Default Description
id str UUID v4 Feedback identifier
agent str "" Agent providing feedback
rating int 3 Rating from 1 (poor) to 5 (excellent)
comment str "" Free-text feedback comment
timestamp datetime now (UTC) When feedback was given

SkillStats

Read-only aggregated view, computed on the fly from .usage.ndjson and .feedback.ndjson.

class SkillStats(BaseModel):
    skill_name: str
    usage_count: int = 0
    avg_rating: float = 0.0
    rating_count: int = 0
    last_used: datetime | None = None
    needs_attention: bool = False
Field Type Default Description
skill_name str required Skill being summarized
usage_count int 0 Total number of usage events
avg_rating float 0.0 Average feedback rating
rating_count int 0 Number of feedback entries
last_used datetime \| None None Timestamp of most recent usage
needs_attention bool False True when avg_rating < 3.0 and rating_count >= 2

Skill Proposals (v0.4.0)

Improvement proposals stored as individual JSON files under .context-teleport/skills/<name>/.proposals/<uuid>.json.

ProposalStatus

class ProposalStatus(str, Enum):
    pending = "pending"
    accepted = "accepted"
    rejected = "rejected"
    upstream = "upstream"
Value Description
pending Proposal awaiting review
accepted Proposal accepted and applied to the skill
rejected Proposal rejected
upstream Proposal pushed as a PR to an upstream repository

SkillProposal

class SkillProposal(BaseModel):
    id: str = Field(default_factory=uuid4)
    skill_name: str
    agent: str = ""
    rationale: str = ""
    proposed_content: str = ""
    diff_summary: str = ""
    status: ProposalStatus = ProposalStatus.pending
    created_at: datetime = Field(default_factory=now)
    resolved_at: datetime | None = None
    resolved_by: str = ""
Field Type Default Description
id str UUID v4 Proposal identifier
skill_name str required Target skill
agent str "" Agent that created the proposal
rationale str "" Why the improvement is needed
proposed_content str "" Full proposed SKILL.md content
diff_summary str "" Human-readable diff summary (computed via difflib)
status ProposalStatus pending Current proposal status
created_at datetime now (UTC) When the proposal was created
resolved_at datetime \| None None When the proposal was accepted/rejected
resolved_by str "" Who resolved the proposal (agent name or "cli")

Context Scoping

Scope is not a model field but sidecar metadata stored in .scope.json files within each directory (knowledge/, knowledge/decisions/, conventions/, skills/).

class Scope(str, Enum):
    public = "public"
    private = "private"
    ephemeral = "ephemeral"
Value Description
public Shared with all agents and pushed to remote (default)
private Excluded from push and export
ephemeral Excluded from push, export, and onboarding

Schema Versioning

The schema version is stored in manifest.json under schema_version. Migrations run automatically when a store is opened with an older version.

Version History

Version Changes
0.1.0 Initial schema
0.2.0 Context scoping via .scope.json sidecar files
0.3.0 Multi-agent support: agent field on KnowledgeEntry, new adapter configs. Backward compatible.
0.4.0 Skill auto-improvement: usage tracking, feedback, proposals. No-op migration (sidecar files created lazily).

Migration Mechanism

Migrations are registered via the @register_migration(from, to) decorator in ctx.core.migrations. The migrate_bundle() function uses BFS to find the shortest path between any two versions and applies each step sequentially.

from ctx.core.migrations import SCHEMA_VERSION, migrate_bundle, check_version_compatible

# Check compatibility
check_version_compatible("0.2.0")  # True -- migration path exists

# Migrate bundle data
migrated = migrate_bundle(bundle_data, target_version=SCHEMA_VERSION)

All migrations from 0.1.0 through 0.4.0 are backward-compatible no-ops: new fields have defaults, and new storage (sidecar files, proposal directories) is created lazily on first write.