What k9x Origin is
k9x Origin is a framework component, and MCP is one of the ways to reach it, the same way k9x Shield is a component, not a service.
What it is
A class in the framework (K9Origin, in k9_origin) that does the
resolving: scope, Shield checks, live state, read-through, verify, flags. It runs inside your solution's own process,
wherever you build it with OriginFactory.create(config).
| Who asks | How | Example |
|---|---|---|
| Orchestrator | Direct, in-process, no network | Before a squad runs, it fills live_context from origin.yaml |
| Agent (or any MCP client) | Over MCP, through OriginMCPServer | The agent's last-moment get_current before acting |
So "MCP service" describes how agents call it, not what it is. OriginMCPServer is the thin MCP
wrapper that carries the calls; all the logic is in K9Origin.
How it is deployed
- In K9X Streams: Origin runs inside the Streams app, and the same process serves it at
/mcp. Agents connect with the framework's MCP client. - In your own solution: the same (Origin inside your app, with its own
/mcp), or added onto an MCP server you already run withregister_on(mcp), so agents use one endpoint for knowing and doing. - At scale: several Origin MCP servers behind a load balancer, all reading shared live state in Redis. Origin keeps no state of its own.
Origin is built into K9-AIF; agents talk to it over MCP; orchestrators call it directly.
Architecture
For solution architects: where k9x Origin sits, how it is filled, and its classes in the framework; then the K9X Streams application on top.
An agent's three paths: knowledge (retriever), current truth (k9x Origin), actions (systems of record), each screened by k9x Shield.
How data reaches k9x Origin: pushed by streams through the Shield gate, pulled from systems of record on demand.
The K9X Streams application
The reference model
K9X Streams is how to build an agentic application that must act on the latest information. Copy the model, not the code: your sources and your projections change; the fabric, the governance and the way agents read live context stay the same.
1 · Data sources
- Databases · change data capture
- ERP, CRM, AP, claims apps · domain events
- Application log files
- IoT devices, telemetry
- SaaS webhooks, REST APIs
- Files, batch drops, S3
- Other agents' results
2 · Kafka topics
- Apache Kafka
- Confluent Platform / Cloud
- Redpanda
- Amazon MSK
3 · K9-AIF context fabric
KafkaEventFabricGovernanceEventGate· k9x Shield; refused → quarantined- your projections → live state · as_of, version, source
- k9x Origin · one call for agents, over MCP
4 · Your K9-AIF solution
- Orchestrator · hands squads
live_context - Squad → agents
- Agent re-checks over MCP · before it pays, approves, ships
- Business rules → HIL on conflicts, quarantine, thresholds
| Source | How it reaches a Kafka topic |
|---|---|
| Relational databases (PostgreSQL, Oracle, SQL Server, MySQL) | Change data capture (Debezium on Kafka Connect): every insert, update and delete becomes an event |
| Business applications | The application publishes domain events, or its own Kafka connector does |
| Application log files | A log shipper (Fluent Bit, Vector, Filebeat) sends each line to a topic |
| IoT devices | An MQTT broker with a Kafka bridge |
| SaaS webhooks and REST APIs | A small receiver or poller publishes to a topic |
| Files and batch drops | A Kafka Connect file or S3 source connector |
| Amazon Kinesis Data Streams | A bridge connector to Kafka (Kinesis Data Firehose only delivers to storage, so agents cannot consume from it) |
Sources keep their own format: an envelope mapper per topic turns each message into an event
(debezium for database change records, log_line for application logs, or your own via
register_mapper); a webhook receiver can skip Kafka with ContextIngest.ingest_raw(). The five steps below build on it.
Five steps
k9x Origin is built into the K9-AIF framework (1.18), like k9x Shield. K9X Streams adds only its Accounts Payable projections, a generator, one orchestrator and one agent. A solution in any domain follows the same five steps.
Switch on k9x Origin
In config/config.yaml. Broker addresses and credentials stay in .env.
origin:
enabled: true
config: config/origin.yaml
state: {provider: redis} # in_memory | redis (shared, LRU-bounded)
streams:
enabled: true # off: Origin answers from systems of record only
provider: kafka
security:
shield: {enabled: true}Describe entities and sources
In config/origin.yaml: what agents may ask about, where each entity's system of record is, what each squad needs, and each agent's scope.
entities:
claim:
key: claim_id
system_of_record: claims_api # read-through on a miss, verify before acting
owned_fields: [status, amount]
conflicts: {status: {transitions: ["approved -> disapproved"]}}
sources:
claims_api: {type: mcp, server: "${CLAIMS_MCP_URL}", tool: get_claim}
squads:
payout: {live_context: ["claim:{claim_id}"], verify_before: [PayoutAgent]}
agents:
PayoutAgent: {allowed_entities: [claim, policy]}One projection per business event
Map source records to the entities your agents reason about. This is the only domain code Origin needs.
class ClaimProjection(BaseContextProjection):
entity, id_field = "claim", "claim_id"
def accepts(self, env): return env.event_type.startswith("claim.")
def project(self, env): return {"status": env.payload["status"],
"amount": env.payload["amount"]}Wire it at the entry point
One factory call. Orchestrators get origin=; agents reach it over MCP with the framework's client. Production refuses an ungoverned Origin.
origin = OriginFactory.create(config, projections=[ClaimProjection()])
ContextIngest(StreamsFactory.create_fabric(config), origin.state,
gate=StreamsFactory.create_gate(config)).attach(["claim.updated"])
orch = PayoutOrchestrator(config, governance=shield, origin=origin)
app.mount("/", OriginMCPServer(origin).http_app()) # MCP at /mcpDecide with business rules
Origin states facts. A rules step in the squad decides, and raises a K9X HIL case when a rule says so. The agent itself makes one call: get_current("claim", "C-1").
rules_agent:
adapter: k9_rules # or rest_rules (a decision service)
hil_queue: claims-review
rules:
- {name: large claim, when: "claim.amount > 5000", then: hil}
- {name: data conflict, when: "live_context.*.flags.conflict", then: hil}Full recipe: Developer Guide 15.8 ↗ · design: k9x Origin design ↗
Your organization's Kafka or Confluent
K9X Streams speaks the Kafka protocol, so it connects to the broker you already run. Set it in .env; no code changes.
| Broker | Settings |
|---|---|
| Apache Kafka, Redpanda | KAFKA_BROKER=host:9092 |
| Confluent Platform | KAFKA_BROKER; plus KAFKA_SECURITY_PROTOCOL and SASL settings if your cluster requires sign-in |
| Confluent Cloud | KAFKA_SECURITY_PROTOCOL=SASL_SSL, KAFKA_SASL_MECHANISM=PLAIN, API key and secret as KAFKA_SASL_USERNAME / KAFKA_SASL_PASSWORD |
| Amazon MSK and others | SASL_SSL with SCRAM-SHA-512 (or your cluster's mechanism) |
| Your topic names | STREAMS_TOPIC_INVOICE, STREAMS_TOPIC_PAYMENT, STREAMS_TOPIC_VENDOR |
Events are JSON today; Avro with Schema Registry is next. The adapter for a broker stays a configuration choice, never a dependency: the framework stays vendor-neutral.
Security controls
Stream data and agent requests are untrusted. Each line of defence is a K9-AIF building block you configure, and each is shown working in the demo's threat simulator.
| Control | Where | Stops | Configure |
|---|---|---|---|
| Stream gate | Before live state | Planted instructions, payment diversion, PII values in events | GovernanceEventGate with PromptInjectionCheck, PaymentDiversionCheck, PIIBoundaryCheck (block_on_match) |
| Quarantine | Live state | A refused event's content ever being read | Automatic: the entity is marked quarantined; agents hold for human review |
| Entity scope | MCP server, before any read | Requests for data outside the server's domain | origin.yaml: agents.<name>.allowed_entities |
| Request screening | MCP server, before any read | Instructions in tool arguments, solicitations for PII | origin.yaml: governance.request_checks (PromptInjectionCheck, PIIRequestCheck) |
| Answer screening | MCP server and agent | PII or instructions in what an agent reads | origin.yaml: governance.answer_checks; screen_tool_result in the agent |
| Source order | Live state | A late event overwriting newer data | Automatic: events apply by when they happened; late ones are kept in history only |
| Conflict flags | Every answer | Acting on data that flipped, arrived out of order, or disagrees with its system of record | origin.yaml: entities.<name>.conflicts; business rules decide (e.g. → HIL) |
Read model, not system of record
Live state is a derived, read-only view of what is true now, rebuilt from Kafka at any time. Agents read it; they act through the systems of record, which emit events that flow back into it.
| Piece | Role |
|---|---|
| Systems of record (ERP, payments, vendor master) | Own the truth and the transactions |
| Kafka topics | Durable, ordered log of every change; use log compaction keyed by entity id for entity state |
| Live state | Derived view with as_of; eventually consistent (milliseconds to seconds) |
| Agents | Read live state; act through the system of record's API |
The agent's re-check shrinks the window for a duplicate action from hours to seconds; the final guarantee belongs in the system of record (an idempotency key or a unique constraint such as one payment per invoice).
Deploying at scale
K9-AIF scales by tier: router cluster, Kafka cluster, orchestrator clusters. K9X Streams adds three tiers that scale the same way.
| Tier | How it scales | State |
|---|---|---|
| Streams ingest workers | One consumer group; topics partitioned by entity key | Stateless: writes to the shared live state |
| Shared live state | Redis cluster, PostgreSQL, or a stream processor's materialized view | The only stateful piece; rebuildable from Kafka |
| Context MCP servers | A pool behind a load balancer | Stateless: reads the shared live state |
| Orchestrators | Existing K9-AIF clusters, each given the shared state as context_state | Stateless |
This demo runs every tier in one process with in-memory state. Shared live state in Redis
(RedisContextState, LRU-bounded, quarantine never evicted) is in K9-AIF 1.18; consumer groups with committed
offsets and lag-based freshness are next.



