Skip to main content
Glama

Echo Memory

A long-horizon memory architecture for AI agents. Echo Memory is built to remember everything an agent has ever learned, in the best possible way, and to keep fetching and writing that memory efficiently no matter how much history accumulates, for coding tools, chatbots, DevOps agents, or any other agentic system, local or deployed.

Why

Every AI agent starts from zero unless something remembers what happened last time, and remembers it well enough and fast enough to still be useful after months or years of accumulated history. Most memory tools solve short-term recall with plain vector search over stored facts. That degrades as history grows: more candidates, more noise, slower retrieval. Echo Memory is built around the read/write algorithm and the data structure that keeps working at long horizons, not just at day one:

  • A temporal, self-consolidating memory graph. Facts are edges between entities, not flat vector rows. Old, rarely-accessed memory doesn't just accumulate: it gets consolidated into higher-level summaries over time (never deleted, always traceable back to the original), so retrieval cost stays bounded by what's currently relevant, not by everything that's ever been written. See docs/designs/echo-memory-design.md for the actual mechanism.

  • Real graph structure, not just similarity. Multi-hop queries like "how did we end up here?", answerable because facts are connected, not just individually embedded.

  • Causal typing, not just similarity. Edges can be tagged caused_by, led_to, blocked_by, contradicts, set by the agent's own read of the conversation, not inferred statistically. Honest about what's tractable today and what isn't.

  • Auditable by design. Every change to memory is logged, with a plain-language reason you can read back (echo-memory why <fact_id>). Memory that consolidates and edits itself is only trustworthy if you can see why.

  • A write path that costs nothing to run. Extraction happens in the calling agent, never on the server, so recording a memory makes zero LLM calls. Measured locally with echo-memory benchmark: write 15ms median, query 8ms, digest 1ms, $0.00 inference cost per episode. The tradeoff is explicit and worth stating: the agent must arrive with entities and facts already extracted, which is more work for the caller and the reason the MCP tool contract spells the shape out. The comparison that makes this matter is Zep/Graphiti, the closest architectural match (bi-temporal edges, fact invalidation, episode provenance): its own published description of ingestion is that "every episode triggers multiple LLM calls for extraction, entity resolution, and invalidation" and that "write cost scales with volume". Here it doesn't.

  • One storage engine, every scale. Postgres + pgvector + Apache AGE, from a single local agent up to an organization-wide shared graph spanning every agent a business runs. No forced migration later. (The novel work is the memory structure and algorithm running on top of Postgres, not a new database engine; see the design doc for why.)

  • Any agent, not one vendor's. The interface is MCP: any MCP-compatible agent can read and write the same memory graph, whether that's a coding assistant, a chatbot, an ops agent, or something built in-house.

Related MCP server: smriti-memcore

Who this is for

  • A developer running local agents who wants Claude Code, Cursor, or anything else to stop losing context between sessions and tools.

  • A team or organization running agentic systems in production (support bots, DevOps agents, internal tooling) that needs a shared memory layer instead of N disconnected ones, with the tenancy model (below) to keep it scoped correctly per agent, per team, or org-wide.

Status

Early and staged. See docs/designs/ for the full architecture and the v1a → v1b build plan. The validated wedge driving v1a is specifically cross-tool coding agent memory (the founder's own daily pain, real and tested). The broader vision above is the target this architecture is built toward, not yet something v1a itself proves. v1a proves basic recall works before v1b adds causal typing and multi-hop graph retrieval, and before v1.1 adds the org-wide tenancy the broader vision depends on.

Getting started

The core recall loop is built and running: write_episode, query_memory, get_audit_log, an MCP server wiring them together, and an echo-memory CLI (why, export). Full setup is in docs/DEVELOPMENT.md; short version:

git clone git@github.com:ayushcodes10/echo-mem.git && cd echo-mem
docker compose up -d                       # Postgres + pgvector + Apache AGE
python -m venv .venv && source .venv/bin/activate && pip install -e ".[dev]"
alembic upgrade head

claude mcp add --scope user echo-memory \
  -e ECHO_MEMORY_USER_ID=your-user-id \
  -e ECHO_MEMORY_AGENT_ID=claude-code \
  -e ECHO_MEMORY_DATABASE_URL="postgresql://postgres:postgres@localhost:5433/echo_memory" \
  -- "$(pwd)/.venv/bin/python" -m echo_memory.server

Start a new Claude Code session and write_episode/query_memory/record_recall_save/ get_audit_log are available across every project, not just this repo.

Wiring a second tool? Give it its own ECHO_MEMORY_AGENT_ID. Cursor should say cursor, Claude Desktop claude-desktop. Memory is shared either way, but a fact records which tool learned it, and two tools claiming the same id makes cross-tool recall impossible to see afterwards. echo-memory adopt wires every MCP client on the machine at once, each with its own id, and shows you the diff before writing anything. echo-memory install --for both does the same for one project.

Prefer it scoped to one project - a single Claude project, a Cursor workspace, a repo whose memory shouldn't mingle with the rest? echo-memory install [path] --for claude|cursor|both writes a project-scoped MCP config plus a skill (or, for Cursor, an always-applied rule) telling the agent when to record and when to recall. See docs/DEVELOPMENT.md for Cursor/per-repo setup, the echo-memory CLI, and running tests; see docs/INTEGRATIONS.md for using Echo Memory from an agent that doesn't speak MCP (a chatbot, a DevOps agent, a booking agent, or any custom tool-calling loop); and see docs/designs/echo-memory-design.md for the current build plan and progress.

The graph

Memory is a graph, not a list of notes. Entities are nodes; a fact is an edge between two of them. That is the whole data model, and everything below follows from it.

The memory graph

Three projects here. checkout-api, mobile-app and data-pipeline were recorded in separate sessions and never told about each other, yet the picture already separates them — because separation is a property of the edges, not a label anyone applied.

Clusters come from structure. Densely connected facts are grouped by label propagation over the edges, and each cluster is named after its most-connected node. That is why data-pipeline sits apart on the left: nothing it knows touches payments. It is also why checkout-api and mobile-app share a cluster despite being different codebases — they genuinely do share an idea, and the graph found it rather than being told.

Components are the stronger claim. Two nodes in different components have no path between them at all, which is the strongest statement this graph can make that two memories are unrelated.

Projects are a facet, not the structure. Every fact records the project it was written from, and you can colour by it, but project says where a fact was written, not what it belongs with.

Click a node: everything it takes part in

A node selected

idempotency keys is the concept that joined those two codebases. The panel shows it referenced from checkout-api twice and mobile-app once, the three facts it appears in, and how the node itself resolved — each mention matched an existing node by exact name rather than creating a duplicate.

Nobody wrote "these projects are related." Two sessions independently recorded a fact about idempotency keys, entity resolution matched them to one node, and the relationship exists as a consequence.

A fact selected

This is what a knowledge graph gives you that a code map cannot. Selecting the edge answers, for that single fact:

what

the sentence, its relation_type, and how confidently it was stated

when

when it became valid, and when it was superseded if it has been

who

which agent wrote it, in which session

where

which project it came from

why

the audit trail — created, superseded from what to what, and the entity-resolution rationale for the nodes at either end

A superseded fact is never deleted. It stops being drawn, because the graph no longer asserts that relationship, but it stays reachable from its node and keeps its full history. echo-memory why <fact_id> prints the same trail in a terminal.

Seeing your own

echo-memory dashboard --serve --open

The images above come from a synthetic dataset (scripts/demo-seed.py) rather than a real store, for the obvious reason: a real memory graph is full of hostnames, account numbers and client names.

Architecture

  • Storage: PostgreSQL with the pgvector and Apache AGE extensions

  • Retrieval: hybrid vector + full-text search (v1a), with Personalized PageRank via networkx added in v1b for multi-hop associative retrieval

  • Interface: Model Context Protocol server: write_episode, query_memory, record_recall_save, get_audit_log

Is the graph in good shape?

echo-memory health

A score, what is strong, what needs attention, and what to do about each, including what recall has cost: how often memory was read, how often a read returned anything, roughly how many tokens were injected, and how many saves those reads produced. Writes were counted from the start; reads were not counted at all, so nothing could answer whether recall earns what it costs. It exists to be run when you have no question - a store can look healthy by every number this CLI reports while most of its facts came from a bulk import, the last real write was a week ago, and only one of several wired agents has ever written anything. --json for machine-readable output.

Nothing in it is gated. The paid tiers sell hosting and the things that only exist when several people share a graph; diagnostics about your own data are not a thing to withhold from the person whose data it is.

Contributing

See CONTRIBUTING.md. Issues and PRs welcome; please read the design docs first so proposals fit the staged build plan. A first pull request is asked to sign the Contributor License Agreement — once, in the PR thread.

License

Apache License 2.0. See LICENSE.

Available Tools

4 tools
get_audit_logC

Human-readable audit trail: what was written, invalidated, superseded, or resolved, and why. since is an ISO8601 timestamp; entries at or after it, chronologically ordered.

ParametersJSON Schema
NameRequiredDescriptionDefault
scopeYes
sinceNo

TDQS

C2.9/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the burden for behavioral disclosure. It usefully explains that results are chronological, filtered by 'since', and cover certain event types. However, it does not disclose pagination, response shape, or the behavior when 'since' is null.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is tight and front-loaded, with the core purpose stated first and the 'since' behavior in the second sentence. It wastes no words, though the missing scope explanation prevents a top score.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a tool with no annotations and no output schema, the description leaves important gaps: it does not define the required 'scope' parameter, describe the return structure, or state how 'since' defaults behave. The event-type list is helpful but only partially completes the picture.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate. It clearly documents the 'since' parameter as an ISO8601 timestamp with ordering semantics, but it does not explain the meaning of the required 'scope' parameter at all, leaving a significant gap.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description identifies the tool as a human-readable audit trail and lists the event categories it covers (written, invalidated, superseded, resolved) plus the rationale. This distinguishes it from the sibling memory operations, though it lacks an explicit retrieval verb like 'gets' or 'returns'.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies this is for viewing historical audit information but gives no explicit guidance on when to choose this over write_episode, query_memory, or record_recall_save. There are no stated exclusions, prerequisites, or alternative routing conditions.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

query_memoryA

Recall prior facts relevant to query, from this agent's own memory (scope="solo") or the pool shared across this user's agents (scope="shared"). Call this at session start, and any other time recalling prior context would save the user from re-explaining something - check here before asking them to repeat themselves or guessing at context you don't have.

digest=True ignores query and returns the most recently written active facts instead, as an opt-in "catch me up" convenience; call it explicitly at session start if you want one, it's never automatic.

A pending_ingest field appears when memory files have been written that the graph hasn't heard about yet. Read each listed file and call write_episode with the entities and facts it states, then mark it done with echo-memory pending --done <path>. The queue exists because extraction needs a model and this server never calls one.

ParametersJSON Schema
NameRequiredDescriptionDefault
queryNo
scopeYes
top_kNo
digestNo

TDQS

A4.6/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does well: it discloses that digest=True ignores the query, returns the most recently written active facts, and is never automatic. It also reveals the pending_ingest field behavior and why the queue exists, which is valuable operational context.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with the main purpose, then usage, then digest, then the pending_ingest workflow. Each paragraph is relevant and non-redundant; the length is justified by the tool's complexity.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description covers purpose, usage timing, scope semantics, digest behavior, and the pending_ingest workflow. It is less complete on the output format of ordinary recall results and top_k semantics, which matters more because there is no output schema.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema has 0% description coverage, so the description must compensate. It explains scope values ('solo' vs 'shared') and the digest parameter's behavior in detail. However, top_k is never mentioned, and the behavior of a null query without digest is left unspecified.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb and resource: 'Recall prior facts relevant to query' from memory. It immediately clarifies the two scopes ('solo' vs 'shared'), making the tool's function unmistakable and distinct from write-oriented siblings like write_episode.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is explicitly prescribed: 'Call this at session start, and any other time recalling prior context would save the user from re-explaining something.' It also tells the agent to check here before asking the user to repeat themselves, and it explains when digest should be invoked explicitly.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

record_recall_saveA

Record that a fact you recalled from memory saved the user from re-explaining something to you.

Call this IN THE SAME TURN, the moment it happens. The trigger is concrete: you called query_memory (or read a memory-derived fact), it answered something the user would otherwise have had to tell you again, and the fact was originally written by a DIFFERENT tool or a past session.

That last part is the whole point, and it is why this takes fact_id rather than a written_by string. Pass the fact_id of the fact that helped - every query_memory result carries one. The server reads that edge's own agent_id and uses it as written_by; the caller does not get to assert who wrote a fact.

Until 2026-08-29 written_by was free text supplied by the caller. Nothing checked the fact existed, so the number gating v1a was a string typed by the model being graded. A fact_id is checkable, so the reading is admissible.

recalled_by is you, defaulting to this server's own agent id. If the fact's author and you are the same tool, the save is still recorded but does not count toward the trial's bar - recalling your own note from ten minutes ago is not the thing being measured.

note should be one sentence naming what it saved re-explaining, written so it still makes sense read cold in six months. Recording the identical note twice is a no-op, so a retry after an error is safe.

Do NOT call this speculatively, for a fact you wrote this session, or because a recall was merely interesting. It is evidence for a gate that decides real build work; an inflated count is worse than an empty one.

ParametersJSON Schema
NameRequiredDescriptionDefault
noteYes
scopeYes
fact_idYes
recalled_byNo

TDQS

A4.5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and handles it thoroughly. It discloses idempotency (duplicate note is a no-op), disqualification (same-tool recalls are recorded but do not count), server-side written_by resolution, and the consequences of inflated counts. It also warns that the caller cannot assert authorship.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with the core action and trigger, then organized into focused paragraphs. It is long, but the behavior is subtle enough to justify the length. The historical note about v1a is arguably redundant for callers, but it does explain why fact_id is required.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description is unusually complete for a recording tool: it explains when, why, and how to call it, including edge cases and disqualifying conditions. It falls short of a 5 only because the required `scope` parameter remains undocumented and success/error behavior is not explicitly described beyond the duplicate no-op.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The description adds meaningful context for fact_id, note, and recalled_by, going well beyond the bare schema. However, schema coverage is 0% and the required `scope` parameter is never explained, leaving a significant gap in a required field.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource: 'Record that a fact you recalled from memory saved the user from re-explaining something to you.' This clearly distinguishes it from siblings like query_memory (which reads memory) and write_episode (which writes memory) by positioning it as a post-recall evidence-recording action.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives an explicit trigger condition: call in the same turn, only when query_memory returned a memory-derived fact that would otherwise require re-explanation, and only when the fact was written by another tool or past session. It also lists clear exclusions: do not call speculatively, for facts written this session, or simply because the recall was interesting.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

write_episodeA

Record something worth remembering later: a decision, a correction, a stated preference, or context that would otherwise have to be re-explained to a different tool or a future session. Call this proactively and immediately when you notice one of these - don't wait to be asked, and don't batch it up for later in the conversation. The cost of a missed memory (re-explaining something later) is higher than the cost of one extra call.

You (the calling agent) extract entities/facts yourself - this server never calls an LLM. Exact shape, every key required unless marked optional:

entities: [{"name": "Postgres", "type": "tool"}, ...]

  • name: non-empty string, unique per entity in this call

  • type: any short string describing what kind of thing this is (e.g. "tool", "person", "decision", "preference") - your choice, not a fixed enum

facts: [{"source": "Decision", "target": "Postgres", "relation_type": "uses", "fact": "decided to use Postgres for storage", "confidence": "extracted"}, ...]

  • source/target: must each exactly match a "name" in entities above

  • relation_type: any short string describing the relationship (e.g. "uses", "prefers", "caused_by") - your choice, not a fixed enum

  • fact: the actual sentence to remember, plain text

  • confidence: MUST be exactly one of "extracted" (directly stated), "inferred" (you deduced it), or "ambiguous" (uncertain) - any other value, including numbers or omitting it, is rejected

entity_resolutions (optional): only needed when a previous call returned ambiguous_entities and you're now confirming which candidate a mention refers to, or that it's new: {"mention name": {"resolved_to": "" | "new"}}. Omit entirely on a call with no prior ambiguity to resolve.

Example call: write_episode(scope="solo", session_id="sess-1", entities=[{"name": "Postgres", "type": "tool"}, {"name": "Decision", "type": "decision"}], facts=[{"source": "Decision", "target": "Postgres", "relation_type": "uses", "fact": "decided to use Postgres for storage", "confidence": "extracted"}])

ParametersJSON Schema
NameRequiredDescriptionDefault
factsYes
scopeYes
entitiesYes
session_idYes
entity_resolutionsNo

TDQS

A4.3/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden and meets it thoroughly. It discloses that the server never calls an LLM and that the calling agent must extract entities/facts itself, specifies that the confidence value must be exactly one of three literal strings or the call is rejected, and explains when entity_resolutions is required. It also documents the strict matching constraint between fact source/target and entity names, giving the agent a clear behavioral model.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long but every sentence earns its place: it front-loads the purpose, gives precise field-by-field shapes and validation rules, and ends with a concrete example. Given that the schema provides no property descriptions, the length is justified and well-structured rather than verbose.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 5-parameter tool with no annotations, no output schema, and an empty schema, the description covers the entities/facts structure, confidence validation, optional entity_resolutions flow, and an example. It falls short on the semantics of scope and session_id, and it does not describe what the tool returns in response, so an agent still has some uncertainty about the complete call contract.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema is nearly empty (objects with additionalProperties: true and 0% description coverage), so the description must compensate. It richly defines entities (name, type, uniqueness), facts (source/target/relation_type/fact/confidence with validation), and entity_resolutions (resolved_to or new). However, the two required parameters scope and session_id are only shown in the example call and never semantically defined, leaving a gap in compensation.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states a specific verb and resource: 'Record something worth remembering later' and enumerates concrete examples (a decision, a correction, a stated preference, or context). It does not explicitly contrast itself with the sibling tool 'record_recall_save', so an agent cannot immediately distinguish between the two write-like tools, which prevents a 5.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives explicit situational triggers: 'Call this proactively and immediately when you notice one of these - don't wait to be asked, and don't batch it up for later in the conversation.' It also explains the cost-benefit rationale for erring on the side of calling. However, it does not mention any alternatives or state when not to use the tool, so it lacks exclusions and sibling routing.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.

  1. 4 tool updatesv0.1.0
    • First observedget_audit_log
    • First observedquery_memory
    • First observedrecord_recall_save
    • First observedwrite_episode

TDQS

A4/5.0
Disambiguation5/5

write_episode creates new memory, query_memory retrieves it, get_audit_log inspects history, and record_recall_save logs a specific recall-save event. Even the two 'record' tools are cleanly separated by what they write: episode facts versus a recall-save reference.

Naming Consistency5/5

All four tools follow the same imperative verb_snake_case convention: write_episode, query_memory, get_audit_log, record_recall_save. There is no mixing of camelCase or inconsistent verb styles.

Tool Count5/5

Four tools is well-scoped for a memory server: a write path, a query path, a history/audit path, and a meta-tracking path. No tool feels redundant, and none is missing for the stated purpose.

Completeness4/5

The core write-query-audit loop is covered, and agents can work around stale facts by writing corrections. The main gaps are the lack of an explicit invalidate/delete tool and the fact that completing the pending-ingest workflow requires an external CLI command, but these are minor rather than fatal.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

Related MCP Servers

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ayushcodes10/echo-mem'

If you have feedback or need assistance with the MCP directory API, please join our Discord server