Skip to main content
Every person who contacts Curia — whether through email, Signal, or the HTTP API — is treated as a contact with an assigned trust level. Before Curia takes any action on a sender’s behalf, it checks who they are, how confident it is in their identity, and whether the channel they’re using is trusted enough for what they’re asking. These checks are deterministic: a sender either has permission or they don’t.
Contacts start as provisional until confirmed — either by you explicitly or by automatic promotion when Curia detects evidence of a real relationship. Provisional contacts have no permissions — Curia will acknowledge their message but will not take any actions on their behalf. You’ll be notified so you can decide whether to confirm, dismiss, or block them.

How trust scores work

Every inbound message from an external sender carries a messageTrustScore between 0.0 and 1.0. Curia computes this score in the dispatch layer before the coordinator ever sees the message. The score combines three inputs:
messageTrustScore =
  (channel trust weight × 0.4)
  + (contact confidence × 0.4)
  − (content risk penalty, up to 0.2)
Channel trust reflects what the authentication protocol can guarantee about sender identity:
ChannelTrust levelNormalized weight
CLIHigh1.0
SignalHigh1.0
HTTP APIMedium0.6
EmailLow0.3
Separately, contact trust levels indicate how much Curia trusts a specific person, regardless of channel. The full hierarchy from lowest to highest:
Trust levelWho gets it
lowDefault for newly confirmed contacts
mediumContacts with moderate interaction history
highContacts you’ve explicitly trusted — EA, CFO, board members
ceoReserved for the CEO. Set automatically at startup via the bootstrap process — cannot be assigned through contact-set-trust.
The ceo trust level outranks all others. It bypasses PII redaction, outbound content filters, and trust score thresholds. The meetsMinimumTrust() check uses ordinal comparison, so ceo satisfies any gate that requires high, medium, or low. Contact confidence accumulates over time as signal builds. A brand-new email sender starts at zero. A CEO-verified Signal contact with a long interaction history may carry 0.95. Contact confidence is computed automatically by the confidence scoring pipeline — it updates on every qualifying event rather than being set manually:
EventEffect
Inbound message receivedIncremental increase based on message history
Outbound reply sentIncremental increase (bidirectional contact)
CEO explicit trust grantLarger positive boost
Verified identity pairing confirmedPositive boost
The pipeline supports two modes: incremental (triggered on each qualifying event, applied as a delta) and full recompute (recalculates from all stored history). Both modes converge to the same value. You never set contact_confidence directly — it is always derived from observable history. Content risk reduces the score when the dispatch layer detects injection-like patterns in the message body. Clean messages incur no penalty.

Score examples

SenderScoreWhy
Brand-new email sender~0.12Low channel trust (0.3 × 0.4) + zero contact confidence
CEO-verified Signal contact~0.80High channel trust (1.0 × 0.4) + high confidence (0.95 × 0.4)
Unknown Signal sender~0.40High channel trust + zero contact confidence
Known email contact (long history)~0.52Low channel trust + high confidence

Action thresholds

The coordinator checks the sender’s messageTrustScore before taking action. If the score falls below the threshold for the requested action, the request is declined.
Action categoryMinimum scoreWhat this means
Information queries0.2Any authenticated message qualifies
Scheduling0.5Medium trust channel or a known email contact
Data export0.8High-trust channel and a verified contact
Financial actions0.8High-trust channel and a verified contact
These thresholds are configured in config/default.yaml under security.trust_thresholds and can be adjusted without a code change. Startup fails if this block is missing or malformed.
Trust thresholds do not apply to you (the CEO). Messages arriving via CLI, or from a contact with the ceo role, are always trusted regardless of score. Curia never explains the trust system or mentions scores to external senders.

Email sender verification

Email is the highest-risk channel because From headers can be spoofed. Curia adds an extra layer on top of the trust score: SPF, DKIM, and DMARC validation. Your email provider performs these checks at the server level. The email channel adapter reads the Authentication-Results headers on every inbound message and maps them to a senderVerified flag:
  • senderVerified: true — SPF, DKIM, and DMARC all pass
  • senderVerified: false — any check fails, or headers are absent (fails closed)
When a message arrives with senderVerified: false, the coordinator will not take consequential actions — financial, data, or access changes — without confirmation via a verified channel like Signal or CLI.

Handling unknown senders

When Curia receives a message from someone not in your contacts, it follows the hold and notify policy for email and Signal: the message is held in a queue, and Curia mentions it to you at the next opportunity.
1

Message arrives from an unknown sender

The dispatch layer looks up the sender’s channel identifier (email address, Signal number) in the contacts database. No match is found. The message is placed in the held messages queue. An audit event is written with the sender identifier, channel, and routing decision.
2

Curia notifies you

At the next conversation, Curia mentions the oldest held message: “By the way, you have a held email from [email protected] about ‘Q3 Numbers’. Want me to identify them?”Curia surfaces one held message at a time — not a list — to avoid interrupting the conversation flow. You can ask for the full list at any time.
3

You decide what to do

You have three options for each held message:
  • Identify — tell Curia who the sender is. Curia creates a confirmed contact and replays the held message through normal processing with full contact context.
  • Dismiss — discard the message. The sender’s provisional contact record is preserved in case they reach out again.
  • Block — discard the message and mark the sender as blocked. Future messages from this sender are dropped without acknowledgment.
4

Confirmed contacts can act

Once you confirm a contact, they gain the permissions assigned to their role. If they send another message, it is processed normally — no more holds.

Automatic contact creation from email

When Curia processes an inbound email, it auto-creates provisional contacts for every participant in the From, To, and CC headers that isn’t already in your contacts. This builds your contact database naturally as emails flow through. To protect against spam campaigns that flood your contacts with thousands of junk entries, auto-creation is rate-limited:
LimitDefaultWhat happens when hit
Per message10Remaining participants in that email are skipped
Per hour100 per email accountAll further auto-creation for that account pauses until the window resets
When a limit is hit, Curia notifies you via email (at most once per limit type per hour). Skipped participants are not lost — they will be auto-created if they email you directly in the future. Both limits are configurable in config/local.yaml:
contact_creation_limits:
  max_per_message: 10
  max_per_hour: 100
Existing contacts are not counted toward the limits — only genuinely new contact creations. If a 20-person thread has 18 known contacts, only the 2 new ones count.

Automatic promotion of provisional contacts

Provisional contacts are automatically promoted to confirmed when Curia detects evidence of a real relationship. Three signals trigger promotion:
  1. Curia outbound — Curia has sent an outbound message to the contact’s email address (any account).
  2. CEO sent email — the CEO has sent them an email directly (detected via a daily Sent folder scan).
  3. Calendar acceptance — the CEO has accepted a calendar event where the contact is an attendee.
A daily 8 AM background sweep by the contacts agent reconciles all provisional contacts against these signals. Promotions can also happen in real-time when Curia sends an outbound message.
Auto-promotion only applies to provisional contacts whose email was established through a trusted source (email participant headers, calendar attendees). It never applies to self-claimed channel identities — those always require explicit CEO confirmation.

Adding contacts proactively

You don’t have to wait for someone to reach out. Tell Curia about people directly in conversation:
“Add Sarah Chen, CFO at Acme. Her email is [email protected] and she’s on Signal at +15550001111.”
Curia creates a contact record, links both channel identities as verified, and assigns the CFO role with its default permissions. You can also update existing contacts:
“Jenna’s new work email is [email protected].”
“Grant Jenna permission to send emails on my behalf.”
All contact mutations are confirmed with you before they’re written, and every change is audit-logged.

Contact profile attributes

Beyond identity and trust, Curia keeps a small set of canonical profile attributes directly on each contact record: preferred name, job title, organization, primary email and phone, timezone, locale, location, pronouns, LinkedIn URL, a short bio, and birthday. These are stored as structured fields on the contact itself — not as free-floating knowledge-graph facts — so agents read a reliable value instead of inferring one from a list of remembered notes. That distinction matters: it’s what stops Curia from guessing a stale address or timezone when composing on your behalf. You set them the same conversational way as anything else (“Sarah’s title is now VP of Sales”, “her timezone is US Pacific”), and Curia writes them via the contact-update skill (phone numbers are normalized to E.164). Richer, narrative knowledge about a person — relationships, history, preferences — still lives in the knowledge graph; the canonical attributes are just the handful of stable fields worth pinning down. The console’s contact view exposes the same fields for direct editing.

Contact deduplication

Curia runs a weekly background scan for likely duplicate contacts — for example, “Jenna Torres” and “J. Torres” who share the same email address. When potential duplicates are detected, Curia presents them to you for review:
“I noticed two contacts that look like the same person: I’d merge them into Jenna Torres (CFO). Want me to proceed?”
Curia never auto-merges contacts. You confirm every merge.

How authorization works

Authorization runs through a three-layer check on every request from a non-CEO sender:
  1. Per-contact overrides — explicit grants and denials you’ve set for this specific contact. These always win over role defaults.
  2. Role defaults — the default permissions and denials for the contact’s role (CFO, board member, direct report, advisor, etc.).
  3. Channel trust gate — even if role and overrides permit an action, the channel must be trusted enough. Financial and data actions require a high-trust channel.
The outcome is one of: Allowed, Denied, Blocked by channel trust, or Needs CEO decision.
Verified contact requests an action


1. Check per-contact overrides
   → Explicit grant? → Allow
   → Explicit denial? → Deny
   → No override? → continue


2. Check role defaults
   → In default_permissions? → Allow
   → In default_deny? → Deny
   → Not listed? → Escalate to CEO


3. Check channel trust gate
   → Channel trust meets action requirement? → Allow
   → Channel trust too low? → Escalate to CEO
Authorization is deterministic. The coordinator cannot override the permission system, even if a request seems reasonable. If the system says “Denied,” the action is denied — Curia declines politely and, if appropriate, lets the sender know they can reach out through a more secure channel.

Channel identity status

Every channel identity (email address, phone number, etc.) linked to a contact carries a status field, separate from whether it has been verified:
StatusMeaning
activeThe identity is current and preferred for outbound use
defunctThe identity is no longer valid (e.g., old email address)
bouncedOutbound delivery to this address has failed
Curia prefers active identities when assembling outbound context. When non-active identities would be used — for example, if a contact’s only linked email is defunct — the Contact Specialist warns the Coordinator before suggesting them. You can update an identity’s status directly:
“Mark sarah’s old acme.com email as defunct — she’s at beacon.com now.”
Curia uses the contact-set-identity-status skill. Status is orthogonal to verification: a verified identity can be defunct, and an active identity may be self_claimed until you confirm it.

Self-claimed identities

If someone messages on a new channel and claims to be an existing contact (“Hi, it’s Jenna”), that identity is tagged self_claimed and treated as unverified until you confirm it. Curia never auto-promotes self-claimed identities to verified status. An unverified identity is gated the same as an unknown sender for any consequential action.

Contact statuses at a glance

StatusWhoPermissions
ProvisionalNew contacts — not yet confirmed by youNone. Curia acknowledges but takes no action.
ConfirmedContacts you’ve verified or that arrived from authoritative sources (CRM, calendar)Role defaults + any per-contact overrides
BlockedContacts you’ve blockedNone. Messages are dropped without response.