Not a wrapper around the GitHub API. A compiler pass — issues, PRs, files, and their real relationships become append-only, evidence-backed knowledge that any MCP-speaking agent can query directly.
A coding agent can grep your issues and diff your PRs, but it re-derives the same facts every session: which files a PR actually touched, which issue a merge closed, what depends on what. None of it persists, and none of it is traceable back to a source.
Object · Relationship · Event · Evidence — everything GitHub emits gets mapped onto these before it ever reaches an agent.
GitHubObserver walks GET /issues?state=all
and GET /pulls/{n}/files, distinguishing issues from PRs the same
way GitHub's own API does. A MockGitHubClient exercises the real
mapping logic with zero network dependency — the same discipline used for every other
connector in the codebase.
"{owner}/{repo}#{number}"[connectors.github] — owner, repo, token env varpub struct GitHubItem { pub number: u64, pub title: String, pub body: String, pub state: String, // "open" | "closed" pub is_pull_request: bool, pub files_changed: Vec<String>, } pub trait GitHubClient: Send + Sync { async fn list_items( &self, owner: &str, repo: &str ) -> Result<Vec<GitHubItem>, GitHubClientError>; }
// PR → changed file References(pr_id, file_id) evidence: "the PR's file-change entry" // body text: "Fixes #12" References(issue_47, issue_12) evidence: "body sentence containing keyword" // deterministic id — stable across re-runs Uuid::new_v5(NAMESPACE, "github:{owner}/{repo}#{n}")
Every PR's files_changed becomes a References
edge to that file's object. Every recognized closing keyword — closes, fixes, resolves —
becomes a References edge between items. No LLM guesses at the
relationship; GitHub already told us what closed what.
ekos recover converges.
The ledger never edits history — it only appends. That single invariant is what makes
ekos_diff possible downstream: "what has changed since T" is a
query over the log, not a guess from two snapshots.
ekos mcp servestdio, newline-delimited JSON-RPC 2.0 — the transport every MCP client already speaks. No network surface, no auth story, no new dependency.
| MCP tool | Answers |
|---|---|
| ekos_search | Find an object by name or kind |
| ekos_neighborhood | What's connected to this, N hops out |
| ekos_dependents | What breaks if this changes — incoming vs. outgoing edges |
| ekos_state | Reconstruct state as of any RFC 3339 timestamp |
| ekos_diff | What knowledge changed since T |
| ekos_ekl | Run a structured knowledge-query expression |
| ekos_status | Ledger entry / object / relationship counts |
The handler holds no write path — an agent can query the ledger but never mutate it.
Every Object and Relationship cites the exact source line that produced it — no unsourced claims.
Nothing is edited in place, so "what changed since T" is a real, replayable query.
The same issue or PR resolves to the same object across every rebuild — no drift, no duplicates.
# compile the repo once $ ekos build # serve it to any MCP client over stdio $ ekos mcp serve --workspace . # point Claude Code / Claude Desktop at it — one line of config { "command": "ekos", "args": ["mcp", "serve"] }