/* * Tardygrada — MCP Server Implementation * JSON-RPC 2.0 over stdin/stdout. * * A Tardygrada program compiles to this. * The world connects. Asks questions. Gets verified responses. */ #include "server.h" #include "vm/util.h" #include "../verify/pipeline.h" #include "../verify/decompose.h" #include "../verify/preprocess.h" #include "../vm/semantic.h" #include "../ontology/inference.h" #include #include #include #include #include #include #include static uint64_t mcp_now_ns(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return (uint64_t)ts.tv_sec * 1000000000ULL + (uint64_t)ts.tv_nsec; } /* Direct write — no stdio */ static void mcp_write(const char *data, int len) { /* MCP stdio transport: Content-Length header + \r\n\r\n + body */ char header[64]; int hlen = 0; const char *prefix = "Content-Length: "; int plen = (int)strlen(prefix); memcpy(header, prefix, plen); hlen = plen; /* itoa for content length */ char num[16]; int nlen = 0; int tmp = len; if (tmp == 0) { num[nlen++] = '0'; } else { while (tmp > 0) { num[nlen++] = '0' + (tmp % 10); tmp /= 10; } } for (int i = nlen - 1; i >= 0; i--) header[hlen++] = num[i]; header[hlen++] = '\r'; header[hlen++] = '\n'; header[hlen++] = '\r'; header[hlen++] = '\n'; tardy_write(STDOUT_FILENO, header, hlen); tardy_write(STDOUT_FILENO, data, len); } /* Build a JSON-RPC response */ static int build_response(char *buf, int buf_size, const char *id, const char *result) { int written = 0; const char *pre = "{\"jsonrpc\":\"2.0\",\"id\":"; int prelen = (int)strlen(pre); if (written + prelen >= buf_size) return -1; memcpy(buf + written, pre, prelen); written += prelen; int idlen = (int)strlen(id); if (written + idlen >= buf_size) return -1; memcpy(buf + written, id, idlen); written += idlen; const char *mid = ",\"result\":"; int midlen = (int)strlen(mid); if (written + midlen >= buf_size) return -1; memcpy(buf + written, mid, midlen); written += midlen; int rlen = (int)strlen(result); if (written + rlen >= buf_size) return -1; memcpy(buf + written, result, rlen); written += rlen; if (written + 1 >= buf_size) return -1; buf[written++] = '}'; buf[written] = '\0'; return written; } /* Build an error response */ static int build_error(char *buf, int buf_size, const char *id, int code, const char *message) { int written = 0; const char *pre = "{\"jsonrpc\":\"2.0\",\"id\":"; int prelen = (int)strlen(pre); if (written + prelen >= buf_size) return -1; memcpy(buf + written, pre, prelen); written += prelen; int idlen = (int)strlen(id); if (written + idlen >= buf_size) return -1; memcpy(buf + written, id, idlen); written += idlen; const char *mid = ",\"error\":{\"code\":"; int midlen = (int)strlen(mid); if (written + midlen >= buf_size) return -1; memcpy(buf + written, mid, midlen); written += midlen; /* code as string */ char codestr[16]; int clen = 0; int neg = 0; int c = code; if (c < 0) { neg = 1; c = -c; } if (c == 0) { codestr[clen++] = '0'; } while (c > 0) { codestr[clen++] = '0' + (c % 10); c /= 10; } if (neg) codestr[clen++] = '-'; for (int i = 0; i < clen / 2; i++) { char t = codestr[i]; codestr[i] = codestr[clen - 1 - i]; codestr[clen - 1 - i] = t; } if (written + clen >= buf_size) return -1; memcpy(buf + written, codestr, clen); written += clen; const char *msgpre = ",\"message\":\""; int mplen = (int)strlen(msgpre); if (written + mplen >= buf_size) return -1; memcpy(buf + written, msgpre, mplen); written += mplen; int mlen = (int)strlen(message); if (written + mlen >= buf_size) return -1; memcpy(buf + written, message, mlen); written += mlen; const char *suf = "\"}}"; int suflen = (int)strlen(suf); if (written + suflen >= buf_size) return -1; memcpy(buf + written, suf, suflen); written += suflen; buf[written] = '\0'; return written; } /* ============================================ * Provenance Helpers * ============================================ */ static const char *trust_str(tardy_trust_t t) { switch (t) { case TARDY_TRUST_MUTABLE: return "mutable"; case TARDY_TRUST_DEFAULT: return "immutable"; case TARDY_TRUST_VERIFIED: return "verified"; case TARDY_TRUST_HARDENED: return "hardened"; case TARDY_TRUST_SOVEREIGN: return "sovereign"; } return "unknown"; } static const char *state_str(tardy_state_t s) { switch (s) { case TARDY_STATE_LIVE: return "live"; case TARDY_STATE_STATIC: return "static"; case TARDY_STATE_TEMP: return "temp"; case TARDY_STATE_DEAD: return "dead"; } return "unknown"; } static const char *type_str(tardy_type_t t) { switch (t) { case TARDY_TYPE_INT: return "int"; case TARDY_TYPE_FLOAT: return "float"; case TARDY_TYPE_STR: return "str"; case TARDY_TYPE_BOOL: return "bool"; case TARDY_TYPE_FACT: return "fact"; case TARDY_TYPE_AGENT: return "agent"; case TARDY_TYPE_UNIT: return "unit"; case TARDY_TYPE_ERROR: return "error"; } return "unknown"; } /* Append _tardy provenance JSON to a result buffer. * buf: buffer with existing JSON (without closing '}') * pos: current write position in buf * buf_size: total buffer size * r: the read result with provenance data * Returns new position, or -1 on overflow. */ static int append_provenance(char *buf, int pos, int buf_size, const tardy_read_result_t *r, const char *ontology_status) { /* Build the birth_hash hex string (first 16 bytes = 32 hex chars) */ char hash_hex[65]; int hbytes = 16; for (int i = 0; i < hbytes; i++) { uint8_t b = r->provenance.birth_hash.bytes[i]; hash_hex[i * 2] = "0123456789abcdef"[b >> 4]; hash_hex[i * 2 + 1] = "0123456789abcdef"[b & 0x0f]; } hash_hex[hbytes * 2] = '\0'; const char *reason = r->provenance.reason ? r->provenance.reason : "unknown"; const char *ont = ontology_status ? ontology_status : "unknown"; /* Format the _tardy object */ char tardy_json[512]; int tlen = snprintf(tardy_json, sizeof(tardy_json), ",\"_tardy\":{" "\"trust\":\"%s\"," "\"state\":\"%s\"," "\"type\":\"%s\"," "\"created_at\":%llu," "\"reason\":\"%s\"," "\"birth_hash\":\"%s\"," "\"ontology\":\"%s\"" "}", trust_str(r->trust), state_str(r->state), type_str(r->type_tag), (unsigned long long)r->provenance.created_at, reason, hash_hex, ont); if (tlen < 0 || pos + tlen >= buf_size) return -1; memcpy(buf + pos, tardy_json, tlen); pos += tlen; return pos; } /* ============================================ * MCP Method Handlers * ============================================ */ /* initialize — MCP handshake */ static int handle_initialize(tardy_mcp_server_t *srv, const char *id) { const char *result = "{\"protocolVersion\":\"2024-11-05\"," "\"capabilities\":{\"tools\":{}}," "\"serverInfo\":{\"name\":\"tardygrada\",\"version\":\"0.1.0\"}}"; int len = build_response(srv->write_buf, TARDY_MCP_BUF_SIZE, id, result); if (len > 0) mcp_write(srv->write_buf, len); return 0; } /* Emit one tool entry into the buffer. Returns bytes written. */ static int emit_tool(char *buf, int buf_size, int pos, const char *name, tardy_agent_t *agent, int *first) { int tlen = pos; if (!*first && tlen < buf_size) buf[tlen++] = ','; *first = 0; const char *pre = "{\"name\":\""; int prelen = (int)strlen(pre); if (tlen + prelen >= buf_size) return tlen; memcpy(buf + tlen, pre, prelen); tlen += prelen; int nlen = (int)strlen(name); if (tlen + nlen >= buf_size) return tlen; memcpy(buf + tlen, name, nlen); tlen += nlen; const char *mid_desc; if (agent && agent->type_tag == TARDY_TYPE_ERROR) mid_desc = "\",\"description\":\"error agent\","; else if (agent && agent->trust >= TARDY_TRUST_SOVEREIGN) mid_desc = "\",\"description\":\"sovereign agent\","; else if (agent && agent->trust >= TARDY_TRUST_VERIFIED) mid_desc = "\",\"description\":\"verified agent\","; else if (agent && agent->trust >= TARDY_TRUST_DEFAULT) mid_desc = "\",\"description\":\"immutable agent\","; else mid_desc = "\",\"description\":\"mutable agent\","; int mdlen = (int)strlen(mid_desc); if (tlen + mdlen >= buf_size) return tlen; memcpy(buf + tlen, mid_desc, mdlen); tlen += mdlen; const char *schema = "\"inputSchema\":{\"type\":\"object\",\"properties\":{}}}"; int slen = (int)strlen(schema); if (tlen + slen >= buf_size) return tlen; memcpy(buf + tlen, schema, slen); tlen += slen; return tlen; } /* tools/list — expose ALL agents in the tree as tools */ static int handle_tools_list(tardy_mcp_server_t *srv, const char *id) { char tools[4096]; int tlen = 0; tools[tlen++] = '{'; const char *tp = "\"tools\":["; int tplen = (int)strlen(tp); memcpy(tools + tlen, tp, tplen); tlen += tplen; int first = 1; /* Walk all agents (skip root at index 0), expose value agents as tools */ for (int i = 1; i < srv->vm->agent_count; i++) { tardy_agent_t *a = &srv->vm->agents[i]; if (a->state == TARDY_STATE_DEAD) continue; if (a->type_tag == TARDY_TYPE_AGENT) continue; /* skip container agents, expose values only */ /* Find this agent's name by scanning all parents' contexts */ const char *name = NULL; for (int p = 0; p < srv->vm->agent_count; p++) { tardy_agent_t *parent = &srv->vm->agents[p]; for (int c = 0; c < parent->context.child_count; c++) { if (parent->context.children[c].agent_id.hi == a->id.hi && parent->context.children[c].agent_id.lo == a->id.lo) { name = parent->context.children[c].name; break; } } if (name) break; } if (name) tlen = emit_tool(tools, sizeof(tools), tlen, name, a, &first); } /* Built-in tools: submit_claim and verify_claim */ { if (!first && tlen < (int)sizeof(tools)) tools[tlen++] = ','; first = 0; const char *sc = "{\"name\":\"submit_claim\"," "\"description\":\"Submit a claim for a pending agent\"," "\"inputSchema\":{\"type\":\"object\"," "\"properties\":{\"agent\":{\"type\":\"string\"}," "\"claim\":{\"type\":\"string\"}}," "\"required\":[\"agent\",\"claim\"]}}"; int sclen = (int)strlen(sc); if (tlen + sclen < (int)sizeof(tools)) { memcpy(tools + tlen, sc, sclen); tlen += sclen; } } { if (!first && tlen < (int)sizeof(tools)) tools[tlen++] = ','; first = 0; const char *vc = "{\"name\":\"verify_claim\"," "\"description\":\"Verify a pending agent's claim through the pipeline\"," "\"inputSchema\":{\"type\":\"object\"," "\"properties\":{\"agent\":{\"type\":\"string\"}}," "\"required\":[\"agent\"]}}"; int vclen = (int)strlen(vc); if (tlen + vclen < (int)sizeof(tools)) { memcpy(tools + tlen, vc, vclen); tlen += vclen; } } /* Built-in tools: send_message and read_inbox */ { if (!first && tlen < (int)sizeof(tools)) tools[tlen++] = ','; first = 0; const char *sm = "{\"name\":\"send_message\"," "\"description\":\"Send a message from one agent to another\"," "\"inputSchema\":{\"type\":\"object\"," "\"properties\":{\"agent_from\":{\"type\":\"string\"}," "\"agent_to\":{\"type\":\"string\"}," "\"payload\":{\"type\":\"string\"}}," "\"required\":[\"agent_from\",\"agent_to\",\"payload\"]}}"; int smlen = (int)strlen(sm); if (tlen + smlen < (int)sizeof(tools)) { memcpy(tools + tlen, sm, smlen); tlen += smlen; } } { if (!first && tlen < (int)sizeof(tools)) tools[tlen++] = ','; first = 0; const char *ri = "{\"name\":\"read_inbox\"," "\"description\":\"Read the next message from an agent's inbox\"," "\"inputSchema\":{\"type\":\"object\"," "\"properties\":{\"agent\":{\"type\":\"string\"}}," "\"required\":[\"agent\"]}}"; int rilen = (int)strlen(ri); if (tlen + rilen < (int)sizeof(tools)) { memcpy(tools + tlen, ri, rilen); tlen += rilen; } } { if (!first && tlen < (int)sizeof(tools)) tools[tlen++] = ','; first = 0; const char *ss = "{\"name\":\"set_semantics\"," "\"description\":\"Set per-agent verification thresholds\"," "\"inputSchema\":{\"type\":\"object\"," "\"properties\":{\"agent\":{\"type\":\"string\"}," "\"key\":{\"type\":\"string\"}," "\"value\":{\"type\":\"string\"}}," "\"required\":[\"agent\",\"key\",\"value\"]}}"; int sslen = (int)strlen(ss); if (tlen + sslen < (int)sizeof(tools)) { memcpy(tools + tlen, ss, sslen); tlen += sslen; } } { if (!first && tlen < (int)sizeof(tools)) tools[tlen++] = ','; first = 0; const char *qa = "{\"name\":\"query_agents\"," "\"description\":\"Search agents by keyword matching\"," "\"inputSchema\":{\"type\":\"object\"," "\"properties\":{\"query\":{\"type\":\"string\"}}," "\"required\":[\"query\"]}}"; int qalen = (int)strlen(qa); if (tlen + qalen < (int)sizeof(tools)) { memcpy(tools + tlen, qa, qalen); tlen += qalen; } } { if (!first && tlen < (int)sizeof(tools)) tools[tlen++] = ','; first = 0; const char *gc = "{\"name\":\"get_conversation\"," "\"description\":\"Read agent conversation history\"," "\"inputSchema\":{\"type\":\"object\"," "\"properties\":{\"agent\":{\"type\":\"string\"}}," "\"required\":[\"agent\"]}}"; int gclen = (int)strlen(gc); if (tlen + gclen < (int)sizeof(tools)) { memcpy(tools + tlen, gc, gclen); tlen += gclen; } } { if (!first && tlen < (int)sizeof(tools)) tools[tlen++] = ','; first = 0; const char *lo = "{\"name\":\"load_ontology\"," "\"description\":\"Load a TTL file into the self-hosted ontology\"," "\"inputSchema\":{\"type\":\"object\"," "\"properties\":{\"path\":{\"type\":\"string\"}}," "\"required\":[\"path\"]}}"; int lolen = (int)strlen(lo); if (tlen + lolen < (int)sizeof(tools)) { memcpy(tools + tlen, lo, lolen); tlen += lolen; } } (void)first; tools[tlen++] = ']'; tools[tlen++] = '}'; tools[tlen] = '\0'; int len = build_response(srv->write_buf, TARDY_MCP_BUF_SIZE, id, tools); if (len > 0) mcp_write(srv->write_buf, len); return 0; } /* tools/call — read an agent's value */ static int handle_tools_call(tardy_mcp_server_t *srv, tardy_json_parser_t *parser, const char *id) { /* Get params.name */ int params_tok = tardy_json_find(parser, 0, "params"); if (params_tok < 0) { int len = build_error(srv->write_buf, TARDY_MCP_BUF_SIZE, id, -32602, "missing params"); if (len > 0) mcp_write(srv->write_buf, len); return -1; } int name_tok = tardy_json_find(parser, params_tok, "name"); if (name_tok < 0) { int len = build_error(srv->write_buf, TARDY_MCP_BUF_SIZE, id, -32602, "missing params.name"); if (len > 0) mcp_write(srv->write_buf, len); return -1; } char tool_name[64]; tardy_json_str(parser, name_tok, tool_name, sizeof(tool_name)); /* ---- Built-in: submit_claim ---- */ if (strcmp(tool_name, "submit_claim") == 0) { int args_tok = tardy_json_find(parser, params_tok, "arguments"); if (args_tok < 0) { int elen = build_error(srv->write_buf, TARDY_MCP_BUF_SIZE, id, -32602, "missing arguments"); if (elen > 0) mcp_write(srv->write_buf, elen); return -1; } int agent_tok = tardy_json_find(parser, args_tok, "agent"); int claim_tok = tardy_json_find(parser, args_tok, "claim"); if (agent_tok < 0 || claim_tok < 0) { int elen = build_error(srv->write_buf, TARDY_MCP_BUF_SIZE, id, -32602, "missing agent or claim"); if (elen > 0) mcp_write(srv->write_buf, elen); return -1; } char agent_name[64]; char claim_text[512]; tardy_json_str(parser, agent_tok, agent_name, sizeof(agent_name)); tardy_json_str(parser, claim_tok, claim_text, sizeof(claim_text)); /* Find and mutate the pending agent */ int mutated = -1; for (int i = 0; i < srv->vm->agent_count; i++) { tardy_agent_t *candidate = tardy_vm_find_by_name( srv->vm, srv->vm->agents[i].id, agent_name); if (candidate && candidate->trust == TARDY_TRUST_MUTABLE) { mutated = tardy_vm_mutate(srv->vm, srv->vm->agents[i].id, agent_name, claim_text, strlen(claim_text) + 1); break; } } if (mutated == 0) { /* Record in agent's conversation */ for (int i = 0; i < srv->vm->agent_count; i++) { tardy_agent_t *candidate = tardy_vm_find_by_name( srv->vm, srv->vm->agents[i].id, agent_name); if (candidate) { tardy_vm_converse(srv->vm, candidate->id, "user", claim_text); break; } } const char *ok_result = "{\"content\":[{\"type\":\"text\"," "\"text\":\"claim submitted\"}]}"; int rlen = build_response(srv->write_buf, TARDY_MCP_BUF_SIZE, id, ok_result); if (rlen > 0) mcp_write(srv->write_buf, rlen); } else { int elen = build_error(srv->write_buf, TARDY_MCP_BUF_SIZE, id, -32000, "agent not found or not mutable"); if (elen > 0) mcp_write(srv->write_buf, elen); } return 0; } /* ---- Built-in: verify_claim ---- */ if (strcmp(tool_name, "verify_claim") == 0) { int args_tok = tardy_json_find(parser, params_tok, "arguments"); if (args_tok < 0) { int elen = build_error(srv->write_buf, TARDY_MCP_BUF_SIZE, id, -32602, "missing arguments"); if (elen > 0) mcp_write(srv->write_buf, elen); return -1; } int agent_tok = tardy_json_find(parser, args_tok, "agent"); if (agent_tok < 0) { int elen = build_error(srv->write_buf, TARDY_MCP_BUF_SIZE, id, -32602, "missing agent"); if (elen > 0) mcp_write(srv->write_buf, elen); return -1; } char agent_name[64]; tardy_json_str(parser, agent_tok, agent_name, sizeof(agent_name)); /* Find the agent */ tardy_agent_t *target = NULL; tardy_uuid_t parent_id = {0, 0}; for (int i = 0; i < srv->vm->agent_count; i++) { tardy_agent_t *candidate = tardy_vm_find_by_name( srv->vm, srv->vm->agents[i].id, agent_name); if (candidate) { target = candidate; parent_id = srv->vm->agents[i].id; break; } } if (!target) { int elen = build_error(srv->write_buf, TARDY_MCP_BUF_SIZE, id, -32000, "agent not found"); if (elen > 0) mcp_write(srv->write_buf, elen); return -1; } /* Read current value */ char claim_buf[512]; memset(claim_buf, 0, sizeof(claim_buf)); tardy_vm_read(srv->vm, parent_id, agent_name, claim_buf, sizeof(claim_buf)); int claim_len = (int)strlen(claim_buf); /* Run verification pipeline */ uint64_t verify_start = mcp_now_ns(); /* Check if caller provided pre-decomposed triples (LLM decomposer) */ tardy_decomposition_t decomps[3]; memset(decomps, 0, sizeof(decomps)); tardy_triple_t all_triples[TARDY_MAX_TRIPLES]; int triple_count = 0; /* Step 1: Subagent decomposer (preprocessor + rule-based). * Strips markdown, extracts key-value pairs, then decomposes. * This is the default -- zero cost, deterministic. */ triple_count = tardy_preprocess_and_decompose( claim_buf, claim_len, all_triples, TARDY_MAX_TRIPLES); /* Also run multi-pass rule decomposer for agreement scoring */ tardy_decompose_multi(claim_buf, claim_len, decomps, 3); /* Merge any triples from multi-pass that aren't already found */ for (int d = 0; d < 3; d++) { for (int t = 0; t < decomps[d].count && triple_count < TARDY_MAX_TRIPLES; t++) { int dup = 0; for (int e = 0; e < triple_count; e++) { if (strcmp(all_triples[e].subject, decomps[d].triples[t].subject) == 0 && strcmp(all_triples[e].predicate, decomps[d].triples[t].predicate) == 0 && strcmp(all_triples[e].object, decomps[d].triples[t].object) == 0) { dup = 1; break; } } if (!dup) all_triples[triple_count++] = decomps[d].triples[t]; } } /* Update decomps for pipeline (uses preprocessor triples) */ for (int t = 0; t < triple_count && t < TARDY_MAX_TRIPLES; t++) { if (t < decomps[0].count) continue; /* already there from multi-pass */ decomps[0].triples[decomps[0].count] = all_triples[t]; decomps[0].count++; decomps[1].triples[decomps[1].count] = all_triples[t]; decomps[1].count++; decomps[2].triples[decomps[2].count] = all_triples[t]; decomps[2].count++; } /* Step 1b: OPTIONAL LLM-assisted decomposition. * Only fires when TARDY_LLM_DECOMPOSE=1 is set AND ANTHROPIC_API_KEY exists. * Default: subagent preprocessor (zero deps, zero cost, instant). * The LLM converts free text to structured triples. */ const char *api_key = getenv("ANTHROPIC_API_KEY"); const char *llm_decompose = getenv("TARDY_LLM_DECOMPOSE"); if (triple_count < 2 && api_key && api_key[0] && llm_decompose && llm_decompose[0] == '1') { /* Build LLM prompt asking for structured triple extraction */ char prompt_body[2048]; snprintf(prompt_body, sizeof(prompt_body), "{\"model\":\"claude-haiku-4-5-20251001\",\"max_tokens\":300," "\"temperature\":0," "\"system\":\"Extract factual claims as JSON triples. " "Output ONLY a JSON array like: " "[{\\\"s\\\":\\\"subject\\\",\\\"p\\\":\\\"predicate\\\",\\\"o\\\":\\\"object\\\"}]. " "Use simple predicates like: created_by, located_in, created_in, " "type_of, has, premiered_on, written_by, invented_by. " "No explanation.\"," "\"messages\":[{\"role\":\"user\",\"content\":\"Extract triples: " "%.800s\"}]}", claim_buf); /* Fork curl to call Claude Haiku (cheapest, fastest) */ int pipefd[2]; if (pipe(pipefd) == 0) { pid_t pid = fork(); if (pid == 0) { close(pipefd[0]); dup2(pipefd[1], STDOUT_FILENO); close(pipefd[1]); int devnull = open("/dev/null", O_WRONLY); if (devnull >= 0) { dup2(devnull, STDERR_FILENO); close(devnull); } char auth[300]; snprintf(auth, sizeof(auth), "x-api-key: %s", api_key); execlp("curl", "curl", "-s", "-X", "POST", "https://api.anthropic.com/v1/messages", "-H", "Content-Type: application/json", "-H", "anthropic-version: 2023-06-01", "-H", auth, "-d", prompt_body, (char *)NULL); _exit(127); } else if (pid > 0) { close(pipefd[1]); char llm_out[4096]; int llm_len = 0; ssize_t n; while ((n = read(pipefd[0], llm_out + llm_len, sizeof(llm_out) - (size_t)llm_len - 1)) > 0) llm_len += (int)n; llm_out[llm_len] = '\0'; close(pipefd[0]); int wstatus; waitpid(pid, &wstatus, 0); /* Parse LLM response -- find the JSON array in content[0].text */ tardy_json_parser_t lp; if (tardy_json_parse(&lp, llm_out, llm_len) == 0) { int ct = tardy_json_find(&lp, 0, "content"); if (ct >= 0) { int ft = ct + 1; int tt = tardy_json_find(&lp, ft, "text"); if (tt >= 0) { char text_buf[2048]; int tlen = tardy_json_str(&lp, tt, text_buf, sizeof(text_buf)); /* Find [ in the text to locate the JSON array */ char *arr_start = strchr(text_buf, '['); if (arr_start && tlen > 0) { tardy_json_parser_t tp; int arr_len = tlen - (int)(arr_start - text_buf); if (tardy_json_parse(&tp, arr_start, arr_len) == 0 && tp.tokens[0].type == TARDY_JSON_ARRAY) { int ac = tp.tokens[0].children; int ti = 1; for (int t = 0; t < ac && triple_count < TARDY_MAX_TRIPLES; t++) { int st = tardy_json_find(&tp, ti, "s"); int pt = tardy_json_find(&tp, ti, "p"); int ot = tardy_json_find(&tp, ti, "o"); if (st >= 0 && pt >= 0 && ot >= 0) { tardy_json_str(&tp, st, all_triples[triple_count].subject, TARDY_MAX_TRIPLE_LEN); tardy_json_str(&tp, pt, all_triples[triple_count].predicate, TARDY_MAX_TRIPLE_LEN); tardy_json_str(&tp, ot, all_triples[triple_count].object, TARDY_MAX_TRIPLE_LEN); triple_count++; } ti += 1 + tp.tokens[ti].children; } /* Update decomps for pipeline */ for (int t = 0; t < triple_count; t++) { decomps[0].triples[t] = all_triples[t]; decomps[1].triples[t] = all_triples[t]; decomps[2].triples[t] = all_triples[t]; } decomps[0].count = triple_count; decomps[1].count = triple_count; decomps[2].count = triple_count; } } } } } } else { close(pipefd[0]); close(pipefd[1]); } } } /* Step 2.5: Try computational verification first */ float comp_confidence = 0.0f; int comp_result = tardy_inference_compute(claim_buf, claim_len, &comp_confidence); /* Step 3: Ground triples against ontology (real or fallback) */ tardy_grounding_t grounding = {0}; tardy_consistency_t consistency = {0}; if (comp_result == 1) { /* Computational claim verified — set grounding as fully grounded */ grounding.count = triple_count; for (int i = 0; i < triple_count && i < TARDY_MAX_TRIPLES; i++) { grounding.results[i].triple = all_triples[i]; grounding.results[i].status = TARDY_KNOWLEDGE_GROUNDED; grounding.results[i].confidence = comp_confidence; grounding.results[i].evidence_count = 1; grounding.grounded++; } consistency.consistent = true; } else if (srv->bridge_connected) { /* External ontology engine via unix socket */ tardy_bridge_verify(&srv->bridge, all_triples, triple_count, &grounding, &consistency); } else if (srv->self_ontology_loaded && srv->self_ontology.triple_count > 0) { /* Self-hosted ontology: triples as agents, no external process */ tardy_self_ontology_verify(&srv->self_ontology, all_triples, triple_count, &grounding, &consistency); } else { /* No ontology available: honest UNKNOWN fallback */ grounding.count = triple_count; for (int i = 0; i < triple_count && i < TARDY_MAX_TRIPLES; i++) { grounding.results[i].triple = all_triples[i]; grounding.results[i].status = TARDY_KNOWLEDGE_UNKNOWN; grounding.results[i].confidence = 0.0f; grounding.results[i].evidence_count = 0; grounding.unknown++; } consistency.consistent = true; } /* Step 4: Real work log — record actual operations */ tardy_work_log_t work_log; tardy_worklog_init(&work_log); work_log.ontology_queries = (srv->bridge_connected || srv->self_ontology.triple_count > 0) ? triple_count : 0; work_log.context_reads = triple_count; work_log.agents_spawned = 0; const tardy_semantics_t *sem = tardy_vm_get_semantics(srv->vm, target->id); tardy_work_spec_t spec = tardy_compute_work_spec(sem); /* Run pipeline 3 times independently for Byzantine consensus */ tardy_pipeline_result_t results[3]; int pass_count = 0; float total_confidence = 0.0f; for (int run = 0; run < 3; run++) { /* Each run uses slightly different decomposition */ tardy_decomposition_t run_decomps[3]; memset(run_decomps, 0, sizeof(run_decomps)); /* Rotate which decomposition is primary for each run */ for (int d = 0; d < 3; d++) { int src_idx = (d + run) % 3; if (src_idx < 3) run_decomps[d] = decomps[src_idx]; } results[run] = tardy_pipeline_verify( claim_buf, claim_len, run_decomps, 3, &grounding, &consistency, &work_log, &spec, sem); if (results[run].passed) { pass_count++; total_confidence += results[run].confidence; } } /* Byzantine majority: 2 of 3 must agree */ int verified = (pass_count >= 2); float avg_confidence = pass_count > 0 ? total_confidence / (float)pass_count : 0.0f; tardy_truth_strength_t final_strength = TARDY_TRUTH_HYPOTHETICAL; if (verified) { /* Use the strongest passing result */ for (int run = 0; run < 3; run++) { if (results[run].passed && results[run].strength > final_strength) final_strength = results[run].strength; } } /* Retry logic — feedback-driven based on failure type */ int max_retries = 2; int retry = 0; while (!verified && retry < max_retries) { tardy_failure_type_t fail = TARDY_FAIL_NONE; /* Get failure type from the best failing result */ for (int run = 0; run < 3; run++) { if (!results[run].passed && results[run].failure_type != TARDY_FAIL_NONE) { fail = results[run].failure_type; break; } } if (fail == TARDY_FAIL_ONTOLOGY_GAP) { /* Can't retry — ontology doesn't have the data */ break; } else if (fail == TARDY_FAIL_DECOMPOSITION) { /* Just break for now — decomposition is deterministic */ break; } else if (fail == TARDY_FAIL_LOW_CONFIDENCE) { /* Lower the threshold slightly for retry */ tardy_semantics_t retry_sem = *sem; retry_sem.truth.min_confidence *= 0.9f; /* Re-run pipeline with relaxed threshold */ pass_count = 0; total_confidence = 0.0f; for (int run = 0; run < 3; run++) { tardy_decomposition_t run_decomps[3]; memset(run_decomps, 0, sizeof(run_decomps)); for (int d = 0; d < 3; d++) run_decomps[d] = decomps[(d + run) % 3]; results[run] = tardy_pipeline_verify( claim_buf, claim_len, run_decomps, 3, &grounding, &consistency, &work_log, &spec, &retry_sem); if (results[run].passed) { pass_count++; total_confidence += results[run].confidence; } } verified = (pass_count >= 2); if (verified) { avg_confidence = total_confidence / (float)pass_count; for (int run = 0; run < 3; run++) if (results[run].passed && results[run].strength > final_strength) final_strength = results[run].strength; } } else { /* Other failures — don't retry blindly */ break; } retry++; } work_log.agents_spawned = 3; /* 3 independent verification runs */ work_log.compute_ns = mcp_now_ns() - verify_start; /* If passed, record provenance and freeze agent to @verified */ if (verified) { /* Record verification results in provenance before freezing */ target->provenance.reason = "verified_claim"; /* Also record the number of grounded triples */ target->provenance.causality_count = grounding.grounded; tardy_vm_freeze(srv->vm, target->id, TARDY_TRUST_VERIFIED); /* Self-growing ontology: add verified triples so future * claims can be grounded against past verifications. * The more you verify, the more the system knows. */ if (srv->self_ontology_loaded) { for (int t = 0; t < triple_count; t++) { tardy_self_ontology_add(&srv->self_ontology, all_triples[t].subject, all_triples[t].predicate, all_triples[t].object); } /* Re-evaluate Datalog to derive new facts from additions */ tardy_dl_evaluate(&srv->self_ontology.datalog); } /* Rule mining: learn patterns from verified claims */ if (triple_count >= 2) { tardy_inference_learn(&srv->ruleset, all_triples, triple_count); } char turn_msg[256]; snprintf(turn_msg, sizeof(turn_msg), "verified: strength=%d confidence=%d%%", (int)final_strength, (int)(avg_confidence * 100)); tardy_vm_converse(srv->vm, target->id, "agent", turn_msg); } else { tardy_vm_converse(srv->vm, target->id, "agent", "verification failed"); } /* Determine failure type string for response */ const char *fail_str = "none"; if (!verified) { /* Find the dominant failure type from results */ tardy_failure_type_t dominant_fail = TARDY_FAIL_NONE; for (int run = 0; run < 3; run++) { if (!results[run].passed && results[run].failure_type != TARDY_FAIL_NONE) { dominant_fail = results[run].failure_type; break; } } switch (dominant_fail) { case TARDY_FAIL_DECOMPOSITION: fail_str = "decomposition_error"; break; case TARDY_FAIL_ONTOLOGY_GAP: fail_str = "ontology_gap"; break; case TARDY_FAIL_CONTRADICTION: fail_str = "contradiction"; break; case TARDY_FAIL_LOW_CONFIDENCE: fail_str = "low_confidence"; break; case TARDY_FAIL_INCONSISTENCY: fail_str = "inconsistency"; break; case TARDY_FAIL_NO_EVIDENCE: fail_str = "no_evidence"; break; case TARDY_FAIL_PROTOCOL: fail_str = "protocol_error"; break; case TARDY_FAIL_LAZINESS: fail_str = "laziness"; break; case TARDY_FAIL_AMBIGUITY: fail_str = "ambiguity"; break; case TARDY_FAIL_CROSS_REP: fail_str = "cross_rep_conflict"; break; default: fail_str = "none"; break; } } /* Ontology status */ const char *ont_status = srv->bridge_connected ? "connected" : (srv->self_ontology.triple_count > 0 ? "self-hosted" : "offline"); /* Build result message */ char result_text[512]; snprintf(result_text, sizeof(result_text), "{\"content\":[{\"type\":\"text\"," "\"text\":\"verified=%s strength=%d confidence=%d%% " "bft=%d/3 triples_grounded=%d/%d " "failure=%s retries=%d ontology=%s\"}]}", verified ? "true" : "false", (int)final_strength, (int)(avg_confidence * 100), pass_count, grounding.grounded, grounding.count, fail_str, retry, ont_status); int rlen = build_response(srv->write_buf, TARDY_MCP_BUF_SIZE, id, result_text); if (rlen > 0) mcp_write(srv->write_buf, rlen); return 0; } /* ---- Built-in: send_message ---- */ if (strcmp(tool_name, "send_message") == 0) { int args_tok = tardy_json_find(parser, params_tok, "arguments"); if (args_tok < 0) { int elen = build_error(srv->write_buf, TARDY_MCP_BUF_SIZE, id, -32602, "missing arguments"); if (elen > 0) mcp_write(srv->write_buf, elen); return -1; } int from_tok = tardy_json_find(parser, args_tok, "agent_from"); int to_tok = tardy_json_find(parser, args_tok, "agent_to"); int pay_tok = tardy_json_find(parser, args_tok, "payload"); if (from_tok < 0 || to_tok < 0 || pay_tok < 0) { int elen = build_error(srv->write_buf, TARDY_MCP_BUF_SIZE, id, -32602, "missing agent_from, agent_to, or payload"); if (elen > 0) mcp_write(srv->write_buf, elen); return -1; } char from_name[64], to_name[64], pay_text[TARDY_MAX_PAYLOAD]; tardy_json_str(parser, from_tok, from_name, sizeof(from_name)); tardy_json_str(parser, to_tok, to_name, sizeof(to_name)); tardy_json_str(parser, pay_tok, pay_text, sizeof(pay_text)); /* Find sender and receiver agents */ tardy_agent_t *sender = NULL; tardy_agent_t *receiver = NULL; for (int i = 0; i < srv->vm->agent_count && (!sender || !receiver); i++) { if (!sender) { tardy_agent_t *c = tardy_vm_find_by_name( srv->vm, srv->vm->agents[i].id, from_name); if (c) sender = c; } if (!receiver) { tardy_agent_t *c = tardy_vm_find_by_name( srv->vm, srv->vm->agents[i].id, to_name); if (c) receiver = c; } } if (!sender || !receiver) { int elen = build_error(srv->write_buf, TARDY_MCP_BUF_SIZE, id, -32000, "sender or receiver not found"); if (elen > 0) mcp_write(srv->write_buf, elen); return -1; } size_t pay_len = strlen(pay_text) + 1; int sent = tardy_vm_send(srv->vm, sender->id, receiver->id, pay_text, pay_len, TARDY_TYPE_STR); if (sent == 0) { const char *ok_result = "{\"content\":[{\"type\":\"text\"," "\"text\":\"message sent\"}]}"; int rlen = build_response(srv->write_buf, TARDY_MCP_BUF_SIZE, id, ok_result); if (rlen > 0) mcp_write(srv->write_buf, rlen); } else { int elen = build_error(srv->write_buf, TARDY_MCP_BUF_SIZE, id, -32000, "send failed (inbox full?)"); if (elen > 0) mcp_write(srv->write_buf, elen); } return 0; } /* ---- Built-in: read_inbox ---- */ if (strcmp(tool_name, "read_inbox") == 0) { int args_tok = tardy_json_find(parser, params_tok, "arguments"); if (args_tok < 0) { int elen = build_error(srv->write_buf, TARDY_MCP_BUF_SIZE, id, -32602, "missing arguments"); if (elen > 0) mcp_write(srv->write_buf, elen); return -1; } int agent_tok = tardy_json_find(parser, args_tok, "agent"); if (agent_tok < 0) { int elen = build_error(srv->write_buf, TARDY_MCP_BUF_SIZE, id, -32602, "missing agent"); if (elen > 0) mcp_write(srv->write_buf, elen); return -1; } char agent_name[64]; tardy_json_str(parser, agent_tok, agent_name, sizeof(agent_name)); /* Find the agent */ tardy_agent_t *target = NULL; for (int i = 0; i < srv->vm->agent_count; i++) { tardy_agent_t *c = tardy_vm_find_by_name( srv->vm, srv->vm->agents[i].id, agent_name); if (c) { target = c; break; } } if (!target) { int elen = build_error(srv->write_buf, TARDY_MCP_BUF_SIZE, id, -32000, "agent not found"); if (elen > 0) mcp_write(srv->write_buf, elen); return -1; } tardy_message_t msg; int got = tardy_vm_recv(srv->vm, target->id, &msg); if (got == 0) { /* Build result with payload and sender info */ char result_text[2048]; /* Escape payload for JSON */ char escaped[TARDY_MAX_PAYLOAD * 2]; int ei = 0; for (size_t pi = 0; pi < msg.payload_len && msg.payload[pi]; pi++) { if (msg.payload[pi] == '"' || msg.payload[pi] == '\\') { if (ei < (int)sizeof(escaped) - 2) escaped[ei++] = '\\'; } if (ei < (int)sizeof(escaped) - 1) escaped[ei++] = msg.payload[pi]; } escaped[ei] = '\0'; snprintf(result_text, sizeof(result_text), "{\"content\":[{\"type\":\"text\"," "\"text\":\"from=%016llx%016llx payload=%s\"}]}", (unsigned long long)msg.from.hi, (unsigned long long)msg.from.lo, escaped); int rlen = build_response(srv->write_buf, TARDY_MCP_BUF_SIZE, id, result_text); if (rlen > 0) mcp_write(srv->write_buf, rlen); } else { const char *empty_result = "{\"content\":[{\"type\":\"text\"," "\"text\":\"inbox empty\"}]}"; int rlen = build_response(srv->write_buf, TARDY_MCP_BUF_SIZE, id, empty_result); if (rlen > 0) mcp_write(srv->write_buf, rlen); } return 0; } /* ---- Built-in: set_semantics ---- */ if (strcmp(tool_name, "set_semantics") == 0) { int args_tok = tardy_json_find(parser, params_tok, "arguments"); if (args_tok < 0) { int elen = build_error(srv->write_buf, TARDY_MCP_BUF_SIZE, id, -32602, "missing arguments"); if (elen > 0) mcp_write(srv->write_buf, elen); return -1; } char aname[64], key[64], val_str[64]; int at = tardy_json_find(parser, args_tok, "agent"); int kt = tardy_json_find(parser, args_tok, "key"); int vt = tardy_json_find(parser, args_tok, "value"); if (at < 0 || kt < 0 || vt < 0) { int elen = build_error(srv->write_buf, TARDY_MCP_BUF_SIZE, id, -32602, "missing agent, key, or value"); if (elen > 0) mcp_write(srv->write_buf, elen); return -1; } tardy_json_str(parser, at, aname, sizeof(aname)); tardy_json_str(parser, kt, key, sizeof(key)); tardy_json_str(parser, vt, val_str, sizeof(val_str)); /* Find agent */ tardy_agent_t *target = NULL; for (int i = 0; i < srv->vm->agent_count; i++) { target = tardy_vm_find_by_name(srv->vm, srv->vm->agents[i].id, aname); if (target) break; } if (!target) { int elen = build_error(srv->write_buf, TARDY_MCP_BUF_SIZE, id, -32000, "agent not found"); if (elen > 0) mcp_write(srv->write_buf, elen); return -1; } /* Clone from global if needed, then set key */ const tardy_semantics_t *current = tardy_vm_get_semantics(srv->vm, target->id); tardy_semantics_t updated = *current; if (strcmp(key, "truth.min_confidence") == 0) { /* Simple float parse */ float f = 0.0f; const char *s = val_str; int neg = 0, i = 0; if (s[0] == '-') { neg = 1; i = 1; } for (; s[i] && s[i] != '.'; i++) f = f * 10.0f + (s[i] - '0'); if (s[i] == '.') { i++; float frac = 0.1f; for (; s[i]; i++) { f += (s[i] - '0') * frac; frac *= 0.1f; } } if (neg) f = -f; updated.truth.min_confidence = f; } else if (strcmp(key, "truth.min_consensus_agents") == 0) { updated.truth.min_consensus_agents = (int)tardy_json_int(parser, vt); } else if (strcmp(key, "truth.min_evidence_triples") == 0) { updated.truth.min_evidence_triples = (int)tardy_json_int(parser, vt); } else if (strcmp(key, "pipeline.min_passing_layers") == 0) { updated.pipeline.min_passing_layers = (int)tardy_json_int(parser, vt); } else { int elen = build_error(srv->write_buf, TARDY_MCP_BUF_SIZE, id, -32602, "unknown semantics key"); if (elen > 0) mcp_write(srv->write_buf, elen); return -1; } tardy_vm_set_semantics(srv->vm, target->id, &updated); const char *ok_result = "{\"content\":[{\"type\":\"text\"," "\"text\":\"semantics updated\"}]}"; int rlen = build_response(srv->write_buf, TARDY_MCP_BUF_SIZE, id, ok_result); if (rlen > 0) mcp_write(srv->write_buf, rlen); return 0; } /* ---- Built-in: query_agents ---- */ if (strcmp(tool_name, "query_agents") == 0) { int args_tok = tardy_json_find(parser, params_tok, "arguments"); if (args_tok < 0) { int elen = build_error(srv->write_buf, TARDY_MCP_BUF_SIZE, id, -32602, "missing arguments"); if (elen > 0) mcp_write(srv->write_buf, elen); return -1; } int query_tok = tardy_json_find(parser, args_tok, "query"); if (query_tok < 0) { int elen = build_error(srv->write_buf, TARDY_MCP_BUF_SIZE, id, -32602, "missing query"); if (elen > 0) mcp_write(srv->write_buf, elen); return -1; } char query_text[256]; tardy_json_str(parser, query_tok, query_text, sizeof(query_text)); tardy_query_result_t qresults[TARDY_MAX_QUERY_RESULTS]; int total_found = 0; for (int qi = 0; qi < srv->vm->agent_count && total_found < TARDY_MAX_QUERY_RESULTS; qi++) { tardy_agent_t *qa = &srv->vm->agents[qi]; if (qa->state == TARDY_STATE_DEAD || qa->context.child_count == 0) continue; tardy_query_result_t batch[TARDY_MAX_QUERY_RESULTS]; int found = tardy_vm_query(srv->vm, qa->id, query_text, batch, TARDY_MAX_QUERY_RESULTS - total_found); for (int qj = 0; qj < found; qj++) { int dup = 0; for (int qk = 0; qk < total_found; qk++) { if (qresults[qk].agent_id.hi == batch[qj].agent_id.hi && qresults[qk].agent_id.lo == batch[qj].agent_id.lo) { dup = 1; if (batch[qj].score > qresults[qk].score) qresults[qk].score = batch[qj].score; break; } } if (!dup && total_found < TARDY_MAX_QUERY_RESULTS) qresults[total_found++] = batch[qj]; } } char result_buf[2048]; int rpos = 0; const char *rpre = "{\"content\":[{\"type\":\"text\",\"text\":\"{\\\"results\\\":["; int rplen = (int)strlen(rpre); memcpy(result_buf + rpos, rpre, rplen); rpos += rplen; for (int qi = 0; qi < total_found; qi++) { if (qi > 0 && rpos < (int)sizeof(result_buf) - 1) result_buf[rpos++] = ','; char entry[128]; int score_int = (int)(qresults[qi].score * 100); int elen = snprintf(entry, sizeof(entry), "{\\\"name\\\":\\\"%s\\\",\\\"score\\\":%d.%02d}", qresults[qi].name, score_int / 100, score_int % 100); if (elen > 0 && rpos + elen < (int)sizeof(result_buf)) { memcpy(result_buf + rpos, entry, elen); rpos += elen; } } const char *rsuf = "]}\"}]}"; int rslen = (int)strlen(rsuf); if (rpos + rslen < (int)sizeof(result_buf)) { memcpy(result_buf + rpos, rsuf, rslen); rpos += rslen; } result_buf[rpos] = '\0'; int rlen = build_response(srv->write_buf, TARDY_MCP_BUF_SIZE, id, result_buf); if (rlen > 0) mcp_write(srv->write_buf, rlen); return 0; } /* ---- Built-in: get_conversation ---- */ if (strcmp(tool_name, "get_conversation") == 0) { int args_tok = tardy_json_find(parser, params_tok, "arguments"); if (args_tok < 0) { int elen = build_error(srv->write_buf, TARDY_MCP_BUF_SIZE, id, -32602, "missing arguments"); if (elen > 0) mcp_write(srv->write_buf, elen); return -1; } int agent_tok = tardy_json_find(parser, args_tok, "agent"); if (agent_tok < 0) { int elen = build_error(srv->write_buf, TARDY_MCP_BUF_SIZE, id, -32602, "missing agent"); if (elen > 0) mcp_write(srv->write_buf, elen); return -1; } char agent_name[64]; tardy_json_str(parser, agent_tok, agent_name, sizeof(agent_name)); /* Find the agent */ tardy_agent_t *conv_target = NULL; for (int i = 0; i < srv->vm->agent_count; i++) { tardy_agent_t *c = tardy_vm_find_by_name( srv->vm, srv->vm->agents[i].id, agent_name); if (c) { conv_target = c; break; } } if (!conv_target) { int elen = build_error(srv->write_buf, TARDY_MCP_BUF_SIZE, id, -32000, "agent not found"); if (elen > 0) mcp_write(srv->write_buf, elen); return -1; } tardy_conversation_turn_t turns[TARDY_MAX_CONVERSATION]; int count = tardy_vm_get_conversation(srv->vm, conv_target->id, turns, TARDY_MAX_CONVERSATION); /* Build JSON array of turns */ char result_buf[4096]; int rpos = 0; const char *rpre = "{\"content\":[{\"type\":\"text\",\"text\":\"["; int rplen = (int)strlen(rpre); memcpy(result_buf + rpos, rpre, rplen); rpos += rplen; for (int ci = 0; ci < count; ci++) { if (ci > 0 && rpos < (int)sizeof(result_buf) - 1) result_buf[rpos++] = ','; char entry[640]; int elen = snprintf(entry, sizeof(entry), "{\\\"role\\\":\\\"%s\\\"," "\\\"content\\\":\\\"%s\\\"," "\\\"at\\\":%llu}", turns[ci].role, turns[ci].content, (unsigned long long)turns[ci].at); if (elen > 0 && rpos + elen < (int)sizeof(result_buf)) { memcpy(result_buf + rpos, entry, elen); rpos += elen; } } const char *rsuf = "]\"}]}"; int rslen = (int)strlen(rsuf); if (rpos + rslen < (int)sizeof(result_buf)) { memcpy(result_buf + rpos, rsuf, rslen); rpos += rslen; } result_buf[rpos] = '\0'; int rlen = build_response(srv->write_buf, TARDY_MCP_BUF_SIZE, id, result_buf); if (rlen > 0) mcp_write(srv->write_buf, rlen); return 0; } /* ---- Built-in: load_ontology ---- */ if (strcmp(tool_name, "load_ontology") == 0) { int args_tok = tardy_json_find(parser, params_tok, "arguments"); if (args_tok < 0) { int elen = build_error(srv->write_buf, TARDY_MCP_BUF_SIZE, id, -32602, "missing arguments"); if (elen > 0) mcp_write(srv->write_buf, elen); return -1; } int path_tok = tardy_json_find(parser, args_tok, "path"); if (path_tok < 0) { int elen = build_error(srv->write_buf, TARDY_MCP_BUF_SIZE, id, -32602, "missing path"); if (elen > 0) mcp_write(srv->write_buf, elen); return -1; } char ttl_path[256]; tardy_json_str(parser, path_tok, ttl_path, sizeof(ttl_path)); int loaded = tardy_self_ontology_load_ttl(&srv->self_ontology, ttl_path); char result_text[256]; if (loaded >= 0) { srv->self_ontology_loaded = true; snprintf(result_text, sizeof(result_text), "{\"content\":[{\"type\":\"text\"," "\"text\":\"loaded %d triples from %s\"}]}", loaded, ttl_path); } else { snprintf(result_text, sizeof(result_text), "{\"content\":[{\"type\":\"text\"," "\"text\":\"failed to load %s\"}]}", ttl_path); } int rlen = build_response(srv->write_buf, TARDY_MCP_BUF_SIZE, id, result_text); if (rlen > 0) mcp_write(srv->write_buf, rlen); return 0; } /* Find agent by name anywhere in the tree */ tardy_agent_t *found_agent = NULL; tardy_read_result_t full_result; memset(&full_result, 0, sizeof(full_result)); full_result.status = TARDY_READ_HASH_MISMATCH; char read_buf[512]; memset(read_buf, 0, sizeof(read_buf)); for (int i = 0; i < srv->vm->agent_count; i++) { tardy_agent_t *candidate = tardy_vm_find_by_name( srv->vm, srv->vm->agents[i].id, tool_name); if (candidate) { found_agent = candidate; full_result = tardy_vm_read_full(srv->vm, srv->vm->agents[i].id, tool_name, read_buf, sizeof(read_buf)); break; } } if (full_result.status == TARDY_READ_OK && found_agent) { char result[2048]; int rlen = 0; const char *pre = "{\"content\":[{\"type\":\"text\",\"text\":\""; int prelen = (int)strlen(pre); memcpy(result + rlen, pre, prelen); rlen += prelen; /* Format value based on type */ if (found_agent->type_tag == TARDY_TYPE_STR || found_agent->type_tag == TARDY_TYPE_ERROR) { /* String: copy directly (escape quotes) */ const char *s = read_buf; while (*s && rlen < (int)sizeof(result) - 10) { if (*s == '"' || *s == '\\') result[rlen++] = '\\'; result[rlen++] = *s++; } } else if (found_agent->type_tag == TARDY_TYPE_BOOL) { int64_t bval; memcpy(&bval, read_buf, sizeof(int64_t)); const char *bs = bval ? "true" : "false"; int bslen = (int)strlen(bs); memcpy(result + rlen, bs, bslen); rlen += bslen; } else { /* Int/Float: format as number string */ int64_t val; memcpy(&val, read_buf, sizeof(int64_t)); char numstr[32]; int nlen = 0; int neg = 0; int64_t v = val; if (v < 0) { neg = 1; v = -v; } if (v == 0) numstr[nlen++] = '0'; while (v > 0) { numstr[nlen++] = '0' + (char)(v % 10); v /= 10; } if (neg) numstr[nlen++] = '-'; for (int j = 0; j < nlen / 2; j++) { char t = numstr[j]; numstr[j] = numstr[nlen - 1 - j]; numstr[nlen - 1 - j] = t; } memcpy(result + rlen, numstr, nlen); rlen += nlen; } /* Close content array, then append _tardy, then close object. * Current result so far: {"content":[{"type":"text","text":"VALUE * We need: ...VALUE"}], "_tardy":{...}} */ const char *arr_close = "\"}]"; int aclen = (int)strlen(arr_close); if (rlen + aclen < (int)sizeof(result)) { memcpy(result + rlen, arr_close, aclen); rlen += aclen; } /* Append provenance */ const char *prov_ont = srv->bridge_connected ? "connected" : (srv->self_ontology.triple_count > 0 ? "self-hosted" : "offline"); int new_rlen = append_provenance(result, rlen, (int)sizeof(result), &full_result, prov_ont); if (new_rlen > 0) rlen = new_rlen; /* Close outer object */ if (rlen < (int)sizeof(result) - 1) result[rlen++] = '}'; result[rlen] = '\0'; int len = build_response(srv->write_buf, TARDY_MCP_BUF_SIZE, id, result); if (len > 0) mcp_write(srv->write_buf, len); } else { tardy_read_status_t status = full_result.status; const char *err; switch (status) { case TARDY_READ_HASH_MISMATCH: err = "hash verification failed"; break; case TARDY_READ_NO_CONSENSUS: err = "no Byzantine consensus"; break; case TARDY_READ_SIG_INVALID: err = "signature invalid"; break; default: err = "read failed"; break; } int len = build_error(srv->write_buf, TARDY_MCP_BUF_SIZE, id, -32000, err); if (len > 0) mcp_write(srv->write_buf, len); } return 0; } /* ============================================ * MCP Server Core * ============================================ */ int tardy_mcp_init(tardy_mcp_server_t *srv, tardy_vm_t *vm) { if (!srv || !vm) return -1; memset(srv, 0, sizeof(tardy_mcp_server_t)); srv->vm = vm; srv->running = 1; /* Try connecting to ontology engines */ int bridge_ok = tardy_bridge_init(&srv->bridge, "/tmp/tardygrada-ontology-sketch.sock", "/tmp/tardygrada-ontology-complete.sock"); srv->bridge_connected = (bridge_ok == 0); /* Log which ontology mode is active (stderr to avoid corrupting MCP) */ if (srv->bridge.dual_mode) { /* both sketch and complete connected */ const char *msg = "[tardygrada] ontology: dual mode (sketch+complete)\n"; tardy_write(STDERR_FILENO, msg, strlen(msg)); } else if (srv->bridge.sketch.connected) { /* sketch only */ const char *msg = "[tardygrada] ontology: sketch only\n"; tardy_write(STDERR_FILENO, msg, strlen(msg)); } else if (srv->bridge.complete.connected) { /* complete only */ const char *msg = "[tardygrada] ontology: complete only\n"; tardy_write(STDERR_FILENO, msg, strlen(msg)); } else { /* no ontology — fallback to UNKNOWN */ const char *msg = "[tardygrada] ontology: none (fallback to UNKNOWN)\n"; tardy_write(STDERR_FILENO, msg, strlen(msg)); } /* Initialize self-hosted ontology (always available as fallback) */ tardy_self_ontology_init(&srv->self_ontology, vm); srv->self_ontology_loaded = srv->self_ontology.initialized; /* Initialize frame registry for CRDT merge */ tardy_frames_init(&srv->frames); /* Initialize inference ruleset with synthetic backbone */ tardy_inference_init(&srv->ruleset); return 0; } int tardy_mcp_handle(tardy_mcp_server_t *srv, const char *request, int len) { tardy_json_parser_t parser; if (tardy_json_parse(&parser, request, len) < 0) return -1; /* Extract method and id */ int method_tok = tardy_json_find(&parser, 0, "method"); int id_tok = tardy_json_find(&parser, 0, "id"); char id_str[32] = "null"; if (id_tok >= 0) { if (parser.tokens[id_tok].type == TARDY_JSON_STRING) { id_str[0] = '"'; tardy_json_str(&parser, id_tok, id_str + 1, sizeof(id_str) - 2); int slen = (int)strlen(id_str); id_str[slen] = '"'; id_str[slen + 1] = '\0'; } else if (parser.tokens[id_tok].type == TARDY_JSON_NUMBER) { memcpy(id_str, parser.tokens[id_tok].start, parser.tokens[id_tok].len); id_str[parser.tokens[id_tok].len] = '\0'; } } if (method_tok < 0) return -1; /* Route to handler */ if (tardy_json_eq(&parser, method_tok, "initialize")) return handle_initialize(srv, id_str); if (tardy_json_eq(&parser, method_tok, "notifications/initialized")) return 0; /* notification, no response */ if (tardy_json_eq(&parser, method_tok, "tools/list")) return handle_tools_list(srv, id_str); if (tardy_json_eq(&parser, method_tok, "tools/call")) return handle_tools_call(srv, &parser, id_str); /* Unknown method */ int rlen = build_error(srv->write_buf, TARDY_MCP_BUF_SIZE, id_str, -32601, "method not found"); if (rlen > 0) mcp_write(srv->write_buf, rlen); return 0; } int tardy_mcp_run(tardy_mcp_server_t *srv) { while (srv->running) { /* Read Content-Length header */ char hdr[128]; int hpos = 0; int content_length = -1; /* Read header line by line */ while (hpos < (int)sizeof(hdr) - 1) { ssize_t n = read(STDIN_FILENO, hdr + hpos, 1); if (n <= 0) { srv->running = 0; return 0; /* EOF — clean shutdown */ } hpos++; /* Check for \r\n\r\n (end of headers) */ if (hpos >= 4 && hdr[hpos - 4] == '\r' && hdr[hpos - 3] == '\n' && hdr[hpos - 2] == '\r' && hdr[hpos - 1] == '\n') { /* Parse Content-Length */ hdr[hpos] = '\0'; const char *cl = "Content-Length: "; char *found = NULL; for (int i = 0; i < hpos - 15; i++) { if (memcmp(hdr + i, cl, 16) == 0) { found = hdr + i + 16; break; } } if (found) { content_length = 0; while (*found >= '0' && *found <= '9') { content_length = content_length * 10 + (*found - '0'); found++; } } break; } } if (content_length <= 0 || content_length >= TARDY_MCP_BUF_SIZE) continue; /* Read body */ int total = 0; while (total < content_length) { ssize_t n = read(STDIN_FILENO, srv->read_buf + total, content_length - total); if (n <= 0) { srv->running = 0; return 0; } total += (int)n; } srv->read_buf[content_length] = '\0'; /* Handle the request */ tardy_mcp_handle(srv, srv->read_buf, content_length); } return 0; } void tardy_mcp_stop(tardy_mcp_server_t *srv) { if (srv) { srv->running = 0; tardy_bridge_shutdown(&srv->bridge); } }