Data Model: Profile Invocation Runtime and Audit Trail

Mission: profile-invocation-runtime-audit-trail-01KPQRX2 Date: 2026-04-21


Entity Overview

ProfileRegistry
    └── 1..* AgentProfile (from AgentProfileRepository, existing)

ProfileInvocationExecutor
    ├── uses ProfileRegistry
    ├── uses ActionRouter
    ├── uses build_charter_context() [existing, charter/context.py]
    └── calls InvocationWriter

InvocationRecord (persisted as JSONL)
    ├── opened by: ProfileInvocationExecutor.invoke()
    └── closed by: InvocationWriter.write_completed()

InvocationPayload (CLI response, ephemeral)
    └── derived from: InvocationRecord + CharterContextResult

RouterDecision | RouterAmbiguityError
    └── produced by: ActionRouter.route()

MinimalViableTrailPolicy (code constant)
    └── tier_1 → InvocationRecord
    └── tier_2 → EvidenceArtifact (optional)
    └── tier_3 → kitty-specs / doctrine artifact (optional)

InvocationSaaSPropagator (background)
    └── consumes: InvocationRecord
    └── produces: ProfileInvocationStarted / ProfileInvocationCompleted (SaaS envelopes)

InvocationRecord (v1)

The canonical Tier 1 audit record. Written as a JSONL file; each line is a JSON object.

File path: .kittify/events/profile-invocations/<invocation_id>.jsonl (Keyed on invocation_id only — no profile_id prefix. Allows profile-invocation complete --invocation-id <id> with no other locator argument. Profile filtering in invocations list reads profile_id from the started-event content.) Immutability rule: lines are append-only. started event is written first; completed event is appended when profile-invocation complete is called.

started event line

FieldTypeRequiredDescription
event"started"Event discriminator
invocation_idstr (ULID)Globally unique invocation identity
profile_idstrProfile that was invoked
actionstrResolved canonical action token
request_textstrOriginal request string from caller
governance_context_hashstrFirst 16 hex chars of SHA-256 of CharterContextResult.text
governance_context_availableboolfalse when DRG/charter is missing
actorstr"claude" \
router_confidence`str \null`
started_atstr (ISO-8601 UTC)Timestamp of invocation start

completed event line

FieldTypeRequiredDescription
event"completed"Event discriminator
invocation_idstr (ULID)Must match started event
outcome`"done" \"failed" \"abandoned" \
evidence_ref`str \null`
completed_atstr (ISO-8601 UTC)Timestamp of completion

State transitions

[no file]  ──→  started event written  ──→  completed event appended
              (executor.invoke())          (profile-invocation complete)

A file with only a started line is a valid open record. A file with both lines is a closed record. A file with multiple started lines or mismatched invocation_id is corrupt — reader must skip with a warning.


InvocationPayload (ephemeral, CLI response)

Returned to the caller by advise, ask, and do. Not persisted.

FieldTypeDescription
invocation_idstrULID — used by caller to call profile-invocation complete
profile_idstrResolved profile identity
profile_friendly_namestrHuman-readable profile name — sourced from AgentProfile.name
actionstrResolved canonical action token
governance_context_textstrFull CharterContextResult.text for this (profile, action) pair
governance_context_hashstrHash matching the started event record
governance_context_availableboolfalse when charter is missing
router_confidence`str \null`

RouterDecision

FieldTypeDescription
profile_idstrResolved profile
actionstrResolved canonical action token
confidence`"exact" \"canonical_verb" \
match_reasonstrHuman-readable description (e.g., "token 'implement' matched IMPLEMENTER canonical verb")

RouterAmbiguityError

Raised (or returned) when the router cannot unambiguously resolve a (profile, action) pair.

FieldTypeDescription
request_textstrOriginal request
error_code"ROUTER_AMBIGUOUS" \"ROUTER_NO_MATCH" \
messagestrHuman-readable explanation
candidateslist[RouterCandidate]Non-empty for ROUTER_AMBIGUOUS; empty for ROUTER_NO_MATCH
suggestionstrE.g., "Use 'spec-kitty ask <profile> <request>' to specify a profile explicitly"

RouterCandidate

FieldTypeDescription
profile_idstrCandidate profile
actionstrCandidate action
match_reasonstrWhy this candidate was surfaced

ProfileDescriptor (for profiles list)

FieldTypeDescription
profile_idstrProfile identity
namestrHuman-readable name — AgentProfile.name (the field is name, not friendly_name)
rolestrRole enum value or custom role string
action_domainslist[str]Canonical verbs + domain keywords combined
source`"shipped" \"project_local"`

MinimalViableTrailPolicy (code constant)

The formal specification of the three-tier audit contract. Immutable frozen dataclass.

MinimalViableTrailPolicy
├── tier_1: TierPolicy
│   ├── name: "every_invocation"
│   ├── mandatory: True
│   ├── description: "One InvocationRecord written locally before executor returns."
│   └── storage_path: ".kittify/events/profile-invocations/<profile_id>-<invocation_id>.jsonl"
├── tier_2: TierPolicy
│   ├── name: "evidence_artifact"
│   ├── mandatory: False
│   ├── description: "Optional EvidenceArtifact for invocations producing checkable output."
│   ├── storage_path: ".kittify/evidence/<invocation_id>/"
│   └── promotion_trigger: "caller sets evidence_ref on profile-invocation complete"
└── tier_3: TierPolicy
    ├── name: "durable_project_state"
    ├── mandatory: False
    ├── description: "Promotion to kitty-specs/ or doctrine when invocation changes project-domain state."
    └── promotion_trigger: "spec, plan, tasks, merge, accept commands only"

EvidenceArtifact (Tier 2, optional)

Created by promote_to_evidence(record, evidence_dir, content).

Directory: .kittify/evidence/<invocation_id>/ Files:

  • evidence.md — content supplied by caller (Markdown)
  • record.json — snapshot of the InvocationRecord at promotion time

Storage Layout

.kittify/
└── events/
    ├── profile-invocations/
    │   ├── 01KPQRX2EVGMRVB4Q1JQBAZJV3.jsonl    # started + completed (profile_id inside)
    │   ├── 01KPQRX3XXXXXXXXXXXXXXXXXX.jsonl     # started only (open)
    │   └── ...
    ├── invocation-index.jsonl                                # optional; added if list latency > 200ms
    └── propagation-errors.jsonl                             # SaaS propagation failures

.kittify/
└── evidence/
    └── 01KPQRX2EVGMRVB4Q1JQBAZJV3/
        ├── evidence.md
        └── record.json

Validation Rules

FieldRule
invocation_idMust be a valid ULID (26 chars, base32 Crockford)
started_at / completed_atMust be ISO-8601 UTC (ends with Z or +00:00)
outcomeMust be one of "done", "failed", "abandoned", or null
event discriminatorMust be "started" or "completed"
Two started events in same fileReader warns and skips second; writer raises InvocationWriteError
invocation_id mismatch in completedReader skips the completed line with warning
evidence_ref pathMust be a relative path under .kittify/evidence/; validated on write