Retires the Moonshot/Kimi subscription in favour of the already-paid ChatGPT plan. Both CLI wrappers now run `codex exec`; the kimi-agent container is gone. adolf-llm + hindsight-llm: - runKimi -> runCodex (`codex exec --json --skip-git-repo-check`), resume via `codex exec resume <thread_id>`. - MCP moves from a per-session .mcp.json (a workaround for Kimi having no --mcp-config-file flag) to a $CODEX_HOME/config.toml generated once at startup from shared-mcp.json. Field translation is load-bearing: bearerTokenEnvVar -> bearer_token_env_var, enabledTools -> enabled_tools. - approval_policy="never" + sandbox_mode required, or unattended turns block on an approval prompt nobody can answer. kimi-agent removed. It was the ONLY large-tier deployment behind LiteLLM, so deleting it outright would have silently degraded every large-tier request to the local 4B model via the existing fallbacks. tier-large, the auto_router complex-reasoning route and their fallbacks now point at the codex-backed adolf-llm wrapper (model_name: codex-agent). Three environment blockers fixed along the way: - OpenAI geo-blocks this host (403 unsupported_country_region_territory). Both containers now egress via the host xray proxy, with NO_PROXY keeping MCP and *.alogins.net traffic off the tunnel. - node:22-slim ships no system CA store; the Rust codex binary validates TLS against it, so every HTTPS call failed with a generic transport error while Node's own fetch worked. ca-certificates added to both images. - `codex exec resume` rejects -C/--cd (plain `codex exec` accepts it), which broke follow-up turns while first turns succeeded. Known regression: Kimi's managed-usage API has no Codex equivalent, so the /usage route returns 501 and there is no quota probe for the codex model. The two quota plugins degrade quietly to no output. Also: stop tracking cognee.env (live LLM + JWT secrets) and gitignore it. The secrets remain in earlier history and should be rotated. Verified live: plain turn, SSE streaming, session resume, MCP tool call, bearer-token MCP call, and completions through both LiteLLM routes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Y5QPagv4iun1ghpwM96Ff
493 lines
32 KiB
YAML
493 lines
32 KiB
YAML
# Agent registry — agents are personas, not queues.
|
|
#
|
|
# Per DESIGN-a2a-agents.md v2.1 §2, §5, §5b (commit 714a9ca7), kanboard task
|
|
# #134 (A2A-2), sibling of #133's model-registry.yaml. An agent is
|
|
# (identity, Card, Policy, State): {persona/system prompt, memory bank(s),
|
|
# tool scope, trust class, preferred tier, current backbone}.
|
|
#
|
|
# THE ONE-FIELD-BACKBONE-SWAP MECHANISM: an agent's `backbone:` field is the
|
|
# ONLY thing here that names a concrete model/runtime. Everything a Card
|
|
# would otherwise duplicate from the model plane (tier the backbone actually
|
|
# delivers, cost_class, availability a(t), context window) is NOT stored
|
|
# statically on the agent — it is resolved at read time by dereferencing
|
|
# `backbone` into model-registry.yaml (via model_registry.py) or, for
|
|
# non-metered flat-subscription runtimes model-registry.yaml deliberately
|
|
# excludes (kb#133: "Claude Code ... belongs in the agent registry"), into
|
|
# the `runtimes:` section below. See agent_registry.py:effective_card().
|
|
# This is what makes "switching a backbone is one field" true rather than
|
|
# aspirational: edit `backbone: kimi` -> `backbone: local-small` and the
|
|
# derived tier/cost/a(t) change with it, with nothing else to keep in sync.
|
|
#
|
|
# `preferred_tier` is a POLICY field (what this persona asks for), distinct
|
|
# from the backbone's actual delivered tier (a FACT, resolved above) — they
|
|
# usually agree but don't have to (e.g. a large-preferring agent temporarily
|
|
# pinned to a small backbone during a quota outage).
|
|
#
|
|
# v2.1: personas + Cards live in git, deployed to runtimes — never
|
|
# live-edited in volumes (kb#156). Several agents below still point at a
|
|
# live-volume persona (adolf's SOUL.md) because #156 (git-deploy pipeline)
|
|
# hasn't landed yet; this registry records that fact, it doesn't fix it.
|
|
#
|
|
# Trust class is on every Card (§5) and is what kb#140 (capability routing)
|
|
# and kb#147 (grant enforcement / vault access) consume — see
|
|
# routing_consumption: at the bottom. Several Cards below (torgash,
|
|
# researcher, elizaveta's KB identity) describe TARGET state for agents/
|
|
# accounts that don't exist yet; each is flagged `status: target` /
|
|
# `note:` rather than silently implying they're live. Building them is
|
|
# explicitly out of kb#134's scope (registry only).
|
|
#
|
|
# Read with agent_registry.py (same directory): load_registry(),
|
|
# get_agent(), effective_card(), trust_rank().
|
|
|
|
schema_version: 1
|
|
|
|
# ── trust classes (§5) ──────────────────────────────────────────────────
|
|
# "human > trusted > sandboxed > untrusted". Numeric rank lets routing/grant
|
|
# code do `>=` comparisons instead of string-matching an ordered list.
|
|
#
|
|
# default_budget_usd/budget_duration (kb#147): defense-in-depth defaults fed
|
|
# into each agent's LiteLLM virtual-key spec (agent_registry.py:
|
|
# litellm_key_spec()) when the agent doesn't override them. Moot for money
|
|
# TODAY (§3a: no metered API by default, only free local-small is reachable
|
|
# without opt-in) but real once anything metered is opted into a tier pool —
|
|
# a budget cap should already exist rather than being bolted on later.
|
|
trust_classes:
|
|
human: { rank: 3, note: "vault access yes; not MCP-tool-scoped, IS the reasoning" }
|
|
trusted: { rank: 2, note: "vault access yes (DECIDED, kb#147); outward actions per ask-first rules", default_budget_usd: 50.0, budget_duration: "30d" }
|
|
sandboxed: { rank: 1, note: "no vault, no outward sends; scoped MCP allowlist; KB access project-scoped", default_budget_usd: 5.0, budget_duration: "30d" }
|
|
untrusted: { rank: 0, note: "anything ingesting the open web; its OUTPUTS are tainted, not just its access restricted", default_budget_usd: 0.0, budget_duration: "30d" }
|
|
|
|
# ── runtimes ─────────────────────────────────────────────────────────────
|
|
# Backbones deliberately OUTSIDE model-registry.yaml's `models:` list.
|
|
# kb#133 excluded Claude Code explicitly: "a flat-subscription runtime, not
|
|
# a metered API deployment — it belongs in the agent registry (#134), not
|
|
# here." This is that home. Same lookup contract as a model-registry entry
|
|
# (id, tier, cost_class, lifecycle, context_tokens) so agent_registry.py's
|
|
# backbone resolver can treat the two sources uniformly.
|
|
runtimes:
|
|
- id: claude-code-cli
|
|
role: "Claude Code CLI — flat Anthropic subscription runtime (this session's own kind)"
|
|
tier: large
|
|
context_tokens: 200000 # current Sonnet/Opus family context window; re-verify on model upgrades
|
|
cost_class: subscription
|
|
lifecycle: always-on # gated by Claude Code's own usage windows, not GPU/quota probes
|
|
quota_probe: "claude-usage (kanboard/bin/claude-usage); orchestration/quota-gating rules documented in kanboard/CLAUDE.md"
|
|
metered: false
|
|
opt_in_required: false
|
|
|
|
# ── agents ───────────────────────────────────────────────────────────────
|
|
agents:
|
|
|
|
# ── Adolf — proactive auditor / personal assistant ──────────────────────
|
|
- id: adolf
|
|
capabilities: [matrix-chat, task-triage, proactive-monitoring, memory-recall, cron-scheduling, browser]
|
|
persona:
|
|
role: "proactive auditor / personal assistant, talks to alvis and elizaveta over Matrix"
|
|
trivial: false
|
|
prompt_source:
|
|
current: "container adolf:/home/node/.openclaw/workspace/SOUL.md — lives in the adolf-state Docker VOLUME, live-editable, zero git history. This is the kb#156 migration-debt state; recorded here per kb#134's brief, not migrated."
|
|
companion_files: ["AGENTS.md", "IDENTITY.md", "TOOLS.md", "USER.md", "HEARTBEAT.md"] # same workspace, same volume, same debt
|
|
target: "git-controlled path once kb#156 lands (e.g. ai/personas/adolf/SOUL.md), deployed read-only into the volume"
|
|
trust_class: trusted
|
|
preferred_tier: large
|
|
backbone: kimi # resolves via model-registry.yaml (adolf-llm Kimi-CLI wrapper endpoint). Swap this ONE field to rebackbone Adolf.
|
|
tool_allowlist:
|
|
mcp_servers: [hindsight, openclaw-tools, kanboard, marketplace, agap]
|
|
gateway_tools: [cron, nodes, browser] # openclaw.json gateway.tools.allow, live 2026-07-21
|
|
vault_access: true # trusted-only per §5 DECIDED; reaches vw_* via the `agap` MCP server
|
|
# kb#144 (A2A-12): PER-TOOL scoping, TWO layers — 2026-07-22, second
|
|
# pass after a live-verified miss on the first. Each server below
|
|
# keeps its own justification comment in its config; this is the
|
|
# registry's copy of the same lists (source of truth this side —
|
|
# validate_capability_grants.py cross-checks against BOTH).
|
|
#
|
|
# Layer 1 — OpenClaw's own mcp.servers.*.toolFilter.include in
|
|
# adolf/openclaw.json, applied when OpenClaw (the `adolf` container)
|
|
# builds ITS OWN tool bundle. Landed first pass, verified schema-valid
|
|
# via `openclaw config validate` / `openclaw mcp probe`. Real, correct
|
|
# for OpenClaw's own client — but NOT what determines the model's
|
|
# actual per-turn context on Adolf's kimi backbone.
|
|
#
|
|
# Layer 2 — shared-mcp.json's per-server `enabledTools`, which
|
|
# adolf-llm/server.js's writeMcpConfig() seeds into each Kimi CLI
|
|
# session's project-root .mcp.json (Gate 1). THIS is the layer that
|
|
# actually reaches the model: Kimi CLI auto-discovers that file, not
|
|
# OpenClaw's config, and applies its own McpServerCommonFields.
|
|
# enabledTools/disabledTools via computeEnabledNames (an allowlist
|
|
# when only enabledTools is set — confirmed by decompiling the
|
|
# installed @moonshot-ai/kimi-code package's dist/main.mjs, both
|
|
# copies of the function, packages/agent-core{,-v2}/src/agent/mcp/
|
|
# connection-manager.ts). The first kb#144 pass got this backwards —
|
|
# see the release comment on kb#144 for the exact wire.jsonl proof
|
|
# (tool counts unchanged post-restart) that caught it: Layer 1 alone
|
|
# is invisible to Kimi.
|
|
#
|
|
# Counts (same tool lists both layers, confirmed identical by
|
|
# validate_capability_grants.py, exit 0):
|
|
# agap 32->28 (includes kb#95 wiki_* and kb#170 todoist_capture_idea),
|
|
# hindsight 29->9, kanboard 23->14, marketplace 13->7 (now in shared-
|
|
# mcp.json, reaches Kimi), openclaw-tools 5->5 (already minimal).
|
|
# Reachable-by-Kimi total (2026-07-26): 9+14+7+5+28=63 tools.
|
|
# Previous total was 52 (excluding marketplace, pre-shared-mcp.json);
|
|
# byte-measure against each server's real tools/list JSON schemas:
|
|
# est. ~9K tokens/turn. Estimate pending the real wire.jsonl number,
|
|
# which needs the adolf-llm container restart alvis owns
|
|
# (shared-mcp.json is bind-mounted read-only but adolf-llm's
|
|
# server.js caches its content at process start, so editing the file
|
|
# alone does not take effect — see capability_grant_status below for
|
|
# the confirm-post-restart command).
|
|
#
|
|
# kb#95 (2026-07-23): added wiki_search/wiki_read/wiki_edit (family
|
|
# MediaWiki / РодоВики, family.alogins.net) to agap-mcp and to both
|
|
# layers' agap allowlist below — Adolf's persona domain (relatives,
|
|
# dates, events), same reasoning as HA/Zabbix/Todoist above. This ages
|
|
# the counts comment above (agap 32->24, total 52) by +3/+3; not
|
|
# recomputed here since it needs the same live wire.jsonl proof kb#144
|
|
# used and this task does not touch the running containers (see
|
|
# shared_mcp_kimi_allowlist below for the exact confirm command).
|
|
#
|
|
# kb#170: added todoist_capture_idea (agap-mcp/src/capture.js —
|
|
# classify with local bge-m3 nearest-centroid, no LLM call, then
|
|
# create the labelled Todoist task in one round trip) to agap-mcp
|
|
# and to both layers' agap allowlist below. Ages the counts comment
|
|
# above by +1/+1 for the same reason as kb#95's note.
|
|
mcp_tool_filter:
|
|
hindsight: [recall, retain, reflect, list_memories, get_memory, update_memory, list_directives, create_directive, delete_directive]
|
|
kanboard: [kanboard_list_projects, kanboard_get_project, kanboard_list_tasks, kanboard_my_tasks, kanboard_get_task, kanboard_search_tasks, kanboard_list_users, kanboard_project_activity, kanboard_create_task, kanboard_update_task, kanboard_move_task, kanboard_change_task_status, kanboard_assign_task, kanboard_add_comment]
|
|
marketplace: [marketplace_find_best, marketplace_search, marketplace_get_product, marketplace_get_recommendations, marketplace_get_reviews, marketplace_compare_prices, marketplace_status]
|
|
agap: [vw_get_password, vw_get_item, vw_list_items, vw_create_login, vw_update_password, ha_get_state, ha_list_entities, ha_call_service, ha_get_history, zabbix_get_problems, zabbix_get_hosts, zabbix_get_items, zabbix_get_triggers, radicale_list_calendars, radicale_list_events, radicale_get_event, radicale_put_event, radicale_delete_event, radicale_move_event, todoist_list_tasks, todoist_list_projects, todoist_create_task, todoist_update_task, todoist_complete_task, todoist_capture_idea, wiki_search, wiki_read, wiki_edit]
|
|
openclaw-tools: null # no filter in openclaw.json — already minimal (5/5 kept)
|
|
note: >
|
|
"scoped core tools" per kb#134's brief, now REAL at both levels: this
|
|
IS ai/openclaw.json's live mcp.servers block (server selection)
|
|
plus its per-server toolFilter.include (tool selection, kb#144). Not
|
|
a narrower aspirational allowlist — validate_capability_grants.py
|
|
cross-checks both against the live config. LiteLLM virtual-key
|
|
budgets remain kb#147's separate job; this field is the input #147
|
|
consumes for MCP scope (litellm_key_spec() handles the model side).
|
|
capability_grant: # kb#147 — the enforcement input for LiteLLM + agap-mcp
|
|
litellm_key_alias: adolf
|
|
mcp_auth_token_env: AGAP_MCP_TOKEN_ADOLF # secret lives in Vaultwarden + this container's env, never in git; see agap-mcp/docker-compose.yml AGAP_MCP_AGENT_TOKENS
|
|
note: >
|
|
Reachable model tiers derived at read time from preferred_tier via
|
|
agent_registry.py:litellm_key_spec() — not duplicated here. Actual
|
|
virtual-key provisioning happens via ai/provision_litellm_keys.py
|
|
against the live LiteLLM proxy; NOT run automatically by this
|
|
registry (privileged action, requires the LiteLLM master key —
|
|
kb#147 handover step, see task comment).
|
|
memory:
|
|
banks:
|
|
- { id: adolf-alvis, role: private, interlocutor: alvis }
|
|
- { id: adolf-elizaveta, role: private, interlocutor: elizaveta }
|
|
- { id: adolf-shared, role: shared, interlocutor: household }
|
|
current_state: >
|
|
NOT split yet for the plugin's recall/retain hooks: a single live
|
|
bank "adolf" (525+ facts) serves every interlocutor today with no
|
|
per-human isolation — the exact defect kb#153 exists to fix
|
|
(depends on this registry existing first). The three banks above
|
|
are kb#153's target, not current fact for the hooks.
|
|
kb#169 (2026-07-26): the SEPARATE raw hindsight MCP tool surface
|
|
(mcp.servers.hindsight in adolf/openclaw.json + ai/shared-
|
|
mcp.json — recall/retain/reflect/etc. callable directly by the
|
|
model, bypassing #153's interlocutor-scoping entirely) has been
|
|
repointed from http://hindsight:8888/mcp/adolf/ (the unpartitioned
|
|
bank, still what the hooks use) to http://hindsight:8888/mcp/
|
|
adolf-shared/ (pre-existing, 0 facts). That surface can now only
|
|
ever touch the shared bank — never a private one, never the mixed
|
|
"adolf" bank — regardless of who's talking to Adolf.
|
|
kb_identity: { username: adolf, user_id: 3 }
|
|
availability_note: "a(t) inherited from backbone at read time (kimi: quota-gated, ~60msg/5h ~300/wk — see model-registry.yaml)"
|
|
|
|
# ── claude-coder — the Claude Code loop as an ordinary consumer ─────────
|
|
- id: claude-coder
|
|
capabilities: [coding, kanboard-dispatch, git, infra-ops, code-review]
|
|
persona:
|
|
role: "implementer — pulls complex coding tasks from the fabric (design §2 theorem 5, 'never special')"
|
|
trivial: false
|
|
prompt_source:
|
|
current: "git-native, layered: ~/.claude/CLAUDE.md (global user memory) + kanboard/CLAUDE.md (canonical kb orchestration ruleset) + per-repo CLAUDE.md files (e.g. agap_git/CLAUDE.md)"
|
|
note: "No single SOUL.md — persona is these CLAUDE.md conventions, already git-controlled. No kb#156 debt for this agent."
|
|
trust_class: trusted
|
|
preferred_tier: large
|
|
backbone: claude-code-cli # resolves via runtimes: above (kb#133 exclusion), NOT model-registry.yaml
|
|
tool_allowlist:
|
|
mcp_servers: [kanboard, agap]
|
|
native_tools: [Bash, Read, Edit, Write, Agent, WebFetch, WebSearch, git]
|
|
vault_access: true # trusted
|
|
note: >
|
|
Widest-scoped agent by design (this session's own tool surface) —
|
|
gated by ask-first rules on outward/destructive actions rather than
|
|
MCP allowlisting. Per kb#147, a real virtual-key budget still applies.
|
|
capability_grant:
|
|
litellm_key_alias: claude-coder
|
|
mcp_auth_token_env: AGAP_MCP_TOKEN_CLAUDE_CODER
|
|
note: "same mechanism as adolf's capability_grant above; see that note."
|
|
memory:
|
|
banks: []
|
|
model: "session (ephemeral, per invocation) + repo state (git history, CLAUDE.md files, kanboard task/comment history) — no persistent Hindsight bank"
|
|
kb_identity: { username: claude, user_id: 2 }
|
|
availability_note: "a(t) inherited from backbone at read time (claude-code-cli: always-on, gated by claude-usage windows)"
|
|
completion_convention: >
|
|
Verified-completion flow (DESIGN v2.1 §2, kb#159): when completing a task,
|
|
the worker/agent NEVER closes it — only moves it to Done (unverified
|
|
completion) and leaves it open. Closing is verification, done by someone
|
|
OTHER than the producer (the submitter, a human, or a reviewer-agent after
|
|
checking acceptance criteria). The fabric-keeper audits this: closed tasks
|
|
where the producer also closed them are flagged as kb#159 violations in
|
|
the daily digest.
|
|
|
|
# ── torgash — marketplace analyst (sandboxed) ───────────────────────────
|
|
- id: torgash
|
|
capabilities: [price-comparison, marketplace-search, cart-ops, product-recommendations]
|
|
persona:
|
|
role: "marketplace analyst — price comparison / shopping across Ozon, Yandex Market, etc."
|
|
trivial: false
|
|
prompt_source:
|
|
current: null
|
|
note: "NOT yet stood up as a running persona — this Card is target state per design §2's agent table and the A2A design-review plan. Building the runtime is out of kb#134's scope (registry only); flagging as follow-up work."
|
|
trust_class: sandboxed
|
|
preferred_tier: small
|
|
backbone: local-small
|
|
tool_allowlist:
|
|
mcp_servers: [marketplace]
|
|
vault_access: false # sandboxed — hard rule §5, no exceptions
|
|
outward_sends: false
|
|
note: "scoped to mcp__marketplace__* tools only; no gitea/ha/zabbix/radicale, no kanboard project outside its own."
|
|
capability_grant:
|
|
litellm_key_alias: torgash
|
|
mcp_auth_token_env: AGAP_MCP_TOKEN_TORGASH
|
|
note: >
|
|
Not provisioned yet (agent not built — see persona.prompt_source
|
|
above). litellm_key_spec('torgash') already resolves correctly
|
|
against the registry today (preferred_tier: small -> models=[local-
|
|
small's litellm_model_name] only, no large/paid-fallback) — verified
|
|
by kb#147's provision_litellm_keys.py --dry-run.
|
|
memory:
|
|
banks: [{ id: torgash, role: private }]
|
|
current_state: "bank not yet created — target state, same as the persona itself"
|
|
kb_identity:
|
|
username: null
|
|
note: "no Kanboard account provisioned yet; §5 calls for KB access project-scoped to a dedicated project once created — gap, not in kb#134's scope."
|
|
availability_note: "a(t) inherited from backbone at read time (local-small: always-on, VRAM-bound)"
|
|
|
|
# ── researcher — autonomous, sandboxed/untrusted-input loop ─────────────
|
|
- id: researcher
|
|
capabilities: [web-research, synthesis, low-priority-background-loop]
|
|
persona:
|
|
role: "autonomous researcher — low-priority self-submitting loop (design §6, §8)"
|
|
trivial: false
|
|
prompt_source: { current: null, note: "not yet built; target-state Card, same caveat as torgash." }
|
|
trust_class: sandboxed
|
|
trust_note: >
|
|
Ingests the open web, so its OUTPUTS are tainted regardless of its own
|
|
sandboxed access level (the untrusted-INPUT rule, §5). Promotion of
|
|
tainted output into a trusted agent's memory or into any action
|
|
requires a gate — initially a task to alvis's inbox.
|
|
preferred_tier: small
|
|
backbone: local-small # escalates to large via always-ask policy (§5) for synthesis — never silent retry-on-bigger-model
|
|
tool_allowlist:
|
|
mcp_servers: [] # target: a scoped web-search/fetch surface once built
|
|
native_tools: [WebSearch, WebFetch]
|
|
vault_access: false
|
|
outward_sends: false
|
|
capability_grant:
|
|
litellm_key_alias: researcher
|
|
mcp_auth_token_env: AGAP_MCP_TOKEN_RESEARCHER
|
|
note: "not provisioned yet (agent not built) — same verification status as torgash's capability_grant above."
|
|
memory:
|
|
banks: [{ id: researcher, role: private }]
|
|
current_state: "bank not yet created — target state"
|
|
kb_identity:
|
|
username: null
|
|
note: "needs its own KB project(s) per §5 ('researcher gets its own KB project(s)') — not yet created, gap outside kb#134's scope."
|
|
availability_note: "a(t) inherited from backbone at read time (local-small: always-on, VRAM-bound)"
|
|
|
|
# ── model-agents — trivial personas (from kb#133) ───────────────────────
|
|
# Design §2 table row 1: "LLM endpoint (kimi, gemma3:4b, ...) | persona:
|
|
# trivial (identity) | memory: none". These are direct-address targets
|
|
# (router mode target=agent-id) for a caller that wants THIS backbone
|
|
# specifically, bypassing any persona/tool-scope layer — distinct from
|
|
# e.g. `adolf`, which happens to use the `kimi` backbone today but adds a
|
|
# persona, memory, and tool scope on top. Only the two chat-capable
|
|
# models get an agent entry; bge-m3/tei-reranker are non-chat sidecars in
|
|
# model-registry.yaml, not addressable A2A targets.
|
|
- id: kimi-endpoint
|
|
capabilities: [raw-completion]
|
|
persona: { role: "trivial identity — the kimi backbone exposed directly, no persona layer", trivial: true, prompt_source: { current: null } }
|
|
trust_class: untrusted # a bare model endpoint makes no trust decisions itself; the caller's trust class governs what reaches it
|
|
preferred_tier: large
|
|
backbone: kimi
|
|
tool_allowlist: { mcp_servers: [], native_tools: [], vault_access: false }
|
|
memory: { banks: [], model: "none (trivial persona)" }
|
|
kb_identity: { username: null }
|
|
|
|
- id: local-small-endpoint
|
|
capabilities: [raw-completion]
|
|
persona: { role: "trivial identity — the local-small backbone exposed directly, no persona layer", trivial: true, prompt_source: { current: null } }
|
|
trust_class: untrusted
|
|
preferred_tier: small
|
|
backbone: local-small
|
|
tool_allowlist: { mcp_servers: [], native_tools: [], vault_access: false }
|
|
memory: { banks: [], model: "none (trivial persona)" }
|
|
kb_identity: { username: null }
|
|
|
|
# ── humans — first-class agents (§2, §5b) ───────────────────────────────
|
|
# "The human being an agent is not a metaphor": approval gates and
|
|
# decisions are ordinary tasks submitted to a human's inbox, which IS the
|
|
# Kanboard column/assignment he already processes. No `backbone` — humans
|
|
# ARE the reasoning, not a resolvable model.
|
|
- id: alvis
|
|
capabilities: [approval, decision, escalation-target]
|
|
persona: { role: "human — primary user/owner of Agap" }
|
|
trust_class: human
|
|
preferred_tier: null
|
|
backbone: null
|
|
tool_allowlist: null # n/a — outranks `trusted`, not MCP-scoped
|
|
memory:
|
|
banks:
|
|
- { id: adolf-alvis, role: private }
|
|
- { id: adolf-shared, role: shared }
|
|
current_state: "today's single unsplit 'adolf' bank mixes alvis + elizaveta content; split pending kb#153"
|
|
matrix_id: "@admin:mtx.alogins.net"
|
|
kb_identity:
|
|
username: admin
|
|
user_id: 1
|
|
note: "assumed == alvis (sole non-bot app-admin account); the Kanboard user record's email field is unpopulated so this can't be confirmed via API — verify by hand if it's ever ambiguous."
|
|
inbox: "tasks assigned to Kanboard user 'admin' (id 1) across projects — his approval/escalation inbox (design §2)"
|
|
availability: "a(t) = waking hours (informal, no fixed function yet); vacation mode sets a(t)=0 and parks his inbox per §5b"
|
|
|
|
- id: elizaveta
|
|
capabilities: [approval, decision, escalation-target]
|
|
persona: { role: "human — household member" }
|
|
trust_class: human
|
|
preferred_tier: null
|
|
backbone: null
|
|
tool_allowlist: null
|
|
memory:
|
|
banks:
|
|
- { id: adolf-elizaveta, role: private }
|
|
- { id: adolf-shared, role: shared }
|
|
current_state: "not yet split out of the single 'adolf' bank — kb#153, flagged urgent-ish there since she is on Adolf's Matrix allowlist TODAY with a shared, unpartitioned bank."
|
|
matrix_id: "@elizaveta:mtx.alogins.net"
|
|
kb_identity:
|
|
username: null
|
|
note: "no Kanboard account provisioned for her yet — her only inbox today is the Matrix DM channel (Adolf's dm.allowFrom), not a KB column. Gap / candidate follow-up task, out of kb#134's scope."
|
|
inbox: "none in Kanboard yet (see kb_identity note above); Matrix DM is the only channel today"
|
|
|
|
# ── memory bank policy (§5b) ─────────────────────────────────────────────
|
|
# Hard rules the hindsight-memory plugin's recall/retain hooks must enforce
|
|
# once kb#153 implements bank selection by interlocutor. Registry mirror of
|
|
# model-registry.yaml's gpu_residency_policy: the parameters a consumer
|
|
# reads, not the enforcement logic itself.
|
|
memory_bank_policy:
|
|
hard_rules:
|
|
- "content from one human's conversations must never surface to another human (correctness property, not preference)"
|
|
- "promotion private -> shared happens only by the owning human's explicit action or an approval task — never automatically"
|
|
- "recall is interlocutor-scoped: the hindsight-memory plugin selects the bank by interlocutor identity (Matrix sender)"
|
|
- "sandboxed agents (torgash, researcher) read at most the shared bank, never any private bank"
|
|
banks:
|
|
- { id: adolf-alvis, owner: alvis, role: private, status: target, note: "kb#153 not yet done" }
|
|
- { id: adolf-elizaveta, owner: elizaveta, role: private, status: target, note: "kb#153 not yet done" }
|
|
- { id: adolf-shared, owner: household, role: shared, status: target, note: "kb#153 not yet done" }
|
|
- { id: adolf, owner: null, role: legacy, status: live-today, note: "single unsplit bank every interlocutor currently reads/writes; superseded by the three rows above once kb#153 lands" }
|
|
- { id: torgash, owner: torgash, role: private, status: target, note: "agent not yet built" }
|
|
- { id: researcher, owner: researcher, role: private, status: target, note: "agent not yet built" }
|
|
|
|
# ── how downstream tasks consume this file ────────────────────────────────
|
|
# Documents the contract kb#140 (routing) and kb#147 (grant enforcement)
|
|
# build against, mirroring model-registry.yaml's `routing:` section.
|
|
routing_consumption:
|
|
trust_gate: >
|
|
kb#147: a task requiring vault access may target only an agent whose
|
|
trust_classes[...].rank >= trust_classes.trusted.rank (i.e. trusted or
|
|
human). Sandboxed/untrusted agents' tool_allowlist.vault_access is
|
|
always false by construction above — kb#147's job is to make that
|
|
provably true at the MCP/LiteLLM enforcement points, not just on paper.
|
|
capability_routing: >
|
|
kb#140: given a task's required capabilities + trust constraint, the
|
|
router filters agents by capabilities ⊆ agent.capabilities, trust rank
|
|
>= required, and current a(t) == 1 (resolved via agent_registry.py's
|
|
effective_card(), which dereferences `backbone` into model-registry.yaml
|
|
or `runtimes:` above) — no submitter-side hardcoded agent id needed.
|
|
|
|
# ── kb#147 (A2A-15) implementation status ────────────────────────────────
|
|
# What "enforced" means as of this task, per enforcement point (§5 lists
|
|
# three: agap-mcp vault tools, OpenClaw per-agent MCP scoping, LiteLLM
|
|
# virtual keys). Recorded here — not just in the KB task comment — because
|
|
# this file is the grants source of truth the design demands.
|
|
capability_grant_status:
|
|
agap_mcp_vault_gate: >
|
|
IMPLEMENTED, tested, NOT ACTIVATED. agap-mcp/src/trust-gate.js resolves
|
|
a bearer token -> agent id -> trust rank (reading THIS file, mounted
|
|
read-only) and agap-mcp/src/server.js gates every vw_* tool behind it.
|
|
Proven with two real, run-now test suites (no live container touched):
|
|
src/trust-gate.test.mjs (pure logic, 10/10) and
|
|
src/trust-gate-http.test.mjs (real HTTP request/response, 4/4) — the
|
|
latter shows a sandboxed-agent token, no token, and an unknown token all
|
|
get "vault access denied" while a trusted-agent token passes. OFF by
|
|
default (AGAP_MCP_ENFORCE_VAULT_TRUST=0 in docker-compose.yml) so this
|
|
change is zero-impact until an operator: (1) generates real per-agent
|
|
bearer tokens, stores them in Vaultwarden, wires them into
|
|
AGAP_MCP_AGENT_TOKENS, (2) sets AGAP_MCP_ENFORCE_VAULT_TRUST=1, (3)
|
|
restarts the agap-mcp container — deliberately not done by this task
|
|
(never restart the live agap-mcp service unattended).
|
|
litellm_virtual_keys: >
|
|
SPEC'D, NOT PROVISIONED. agent_registry.py:litellm_key_spec() computes
|
|
each agent's model allow-list (derived from preferred_tier x
|
|
model-registry.yaml routing.tiers, non-metered only unless opted in)
|
|
and a default budget from trust_classes[...].default_budget_usd.
|
|
ai/provision_litellm_keys.py turns that spec into LiteLLM
|
|
/key/generate calls. Verified with --dry-run (prints the exact payload
|
|
per agent, no network call) — actually creating keys needs
|
|
LITELLM_MASTER_KEY against the live proxy, a privileged write this task
|
|
does not perform unattended; see the kb#147 task comment for the exact
|
|
command to run once approved.
|
|
openclaw_mcp_allowlist: >
|
|
STRUCTURAL at both server AND tool level, cross-checked (kb#144 extended
|
|
this from server-only). adolf/openclaw.json's mcp.servers block is
|
|
adolf's real, live MCP surface (git-controlled): its server set matches
|
|
tool_allowlist.mcp_servers, and each server's toolFilter.include (added
|
|
kb#144 first pass) matches tool_allowlist.mcp_tool_filter — exactly.
|
|
ai/validate_capability_grants.py checks this automatically, read-only,
|
|
no live changes. Real and correct for OpenClaw's OWN MCP client surface —
|
|
but per the kb#144 first-pass release comment's wire.jsonl proof, this
|
|
layer alone does NOT reach the model on Adolf's kimi backbone (see
|
|
shared_mcp_kimi_allowlist below, the layer that does). NOT restarted by
|
|
this task for this file's change either way (openclaw.json is
|
|
bind-mounted read-only as the live config; `docker compose restart adolf`
|
|
is the activation step, alvis's call).
|
|
shared_mcp_kimi_allowlist: >
|
|
STRUCTURAL, cross-checked, NOT YET ACTIVATED — kb#144 SECOND pass
|
|
(2026-07-22), added after live verification (restart + one real turn,
|
|
wire.jsonl inspection) proved the first pass's openclaw.json-only fix
|
|
left Kimi's actual tool bundle unchanged (23/32/29/5, not 14/24/9/5).
|
|
Root cause: Kimi CLI auto-discovers a project-root `.mcp.json` that
|
|
adolf-llm/server.js's writeMcpConfig() seeds from ai/shared-mcp.json
|
|
— a completely separate config from openclaw.json, read by a separate
|
|
MCP client (Kimi CLI inside the adolf-llm container, not OpenClaw inside
|
|
the adolf container). shared-mcp.json now carries the same per-server
|
|
tool lists as `enabledTools` (Kimi's own allowlist field —
|
|
McpServerCommonFields.enabledTools, applied via computeEnabledNames;
|
|
confirmed by decompiling the installed @moonshot-ai/kimi-code package's
|
|
dist/main.mjs, both copies of the function/schema, no live container
|
|
touched). ai/validate_capability_grants.py now cross-checks THIS
|
|
file too (load_shared_mcp_enabled_tools), same exit-0-or-fail contract
|
|
as the openclaw.json check. marketplace is absent from shared-mcp.json
|
|
entirely (pre-existing: Kimi's session never had it) — not asserted by
|
|
the validator for that server, by design, not a gap this task opened.
|
|
NOT YET ACTIVATED: shared-mcp.json IS bind-mounted read-only into
|
|
adolf-llm (`./shared-mcp.json:/shared-mcp.json:ro` in
|
|
ai/docker-compose.yml) so the file on disk is already what the
|
|
container would read — but adolf-llm/server.js loads it ONCE into a
|
|
module-level variable at process start (not per-request), so editing the
|
|
file alone does not take effect; `docker compose restart adolf-llm` is
|
|
the only remaining step, deliberately not run by this task (never
|
|
restart a live service unattended). Confirm the real post-restart
|
|
per-turn token delta via the adolf-llm container's Kimi session wire
|
|
log: `docker exec adolf-llm sh -c "tail -1
|
|
/root/.kimi-code/sessions/*/agents/main/wire.jsonl"` (after one real
|
|
turn against a NEW session, since existing sessions' .mcp.json is
|
|
rewritten on their next turn too) and compare per-server tool counts
|
|
against 9/14/5/24 (hindsight/kanboard/openclaw-tools/agap) — the exact
|
|
same command the first-pass verification used to catch the miss.
|