Sign in to edit tickets from this page.

← all tickets · home

Make Chukwa consumer MCP tools self-documenting and align the tool surface with actual behavior

resolved 05be4ec5-8edf-4508-ba3c-611e493dd974

created_at
2026-04-30
updated_at
2026-04-30
priority
P1
ticket_type
bug
resolved_at
2026-04-30
resolution
accepted

Body

Ticket: Make Chukwa consumer MCP tools self-documenting and align the tool surface with actual behavior

Summary

Fix the consumer-facing Chukwa MCP tool surface so a caller with access only to the consumer tools can understand how to use them without operator tools, source-code access, or tribal knowledge.

The primary work is in the MCP tool manifest, input schemas, descriptions, examples, response shapes, and MCP-boundary validation. Keep the blast radius focused on the visible tool contract. Change underlying business logic only where the current tool surface advertises, accepts, or returns misleading data.

Use the uploaded chukwa-repo(8).zip version as the baseline.


Goal

Make tools/list for the consumer MCP surface usable as a self-documenting contract.

A consumer-only caller must be able to answer these questions from the tool surface alone:

  1. What does this tool do?
  2. When do I use it?
  3. What exact JSON shape do I pass?
  4. What does it return?
  5. What tool do I call next?
  6. Which identifiers, hashes, labels, cursors, and refs are accepted?
  7. How do I author a workflow-backed scenario without reading Rust code?

Scope

In scope

Update the consumer MCP tool surface:

Consumer tools in scope:

put_cognition_profile
put_environment
put_entity
put_json_schema
put_response_source
put_cognition_workflow
get_cognition_profile
get_environment
get_entity
get_json_schema
get_response_source
get_cognition_workflow
assemble_scenario
fork_scenario
list_scenarios
get_scenario
lineage_of
children_of
create_world
list_worlds
get_world
delete_world
run_turn
get_turn_status
list_attempts
get_turn
list_turns
diff_turns
get_state_at
get_events
get_world_entity
entity_history

Out of scope

Do not use this ticket for broad simulation refactors.

Do not rewrite the kernel, workflow runner, world-store business logic, LLM routing, turn execution, or scenario derivation semantics.

Do not clean up zombie code, historical structs, old comments, old migrations, or old internal placeholders unless that code path is directly exposed through a consumer tool input, output, description, or schema.

Do not add new consumer tools in this ticket. Embed reference material, WorldPatch shape, and workflow examples into the existing manifest descriptions and examples.

Do not require a separate guide for basic tool use. A guide can exist later, but the consumer tool surface must stand on its own.


Required implementation

1. Make tool_manifest() the consumer contract

Rewrite every consumer tool description using this exact structure:

Purpose: ...
Use when: ...
Input: ...
Returns: ...
Next: ...
Notes: ...

Every description must name the next likely tool call.

Examples:

Remove consumer-visible internal project language from descriptions and schemas, including:

Phase
ticket
aggressive-cull
docs/terms.md
code-navigator
operator
legacy fixture porting

Keep that context in internal tickets or comments only. Do not expose it through consumer MCP tools.


2. Replace bare object schemas with explicit inline schemas

No required consumer input field may be documented as only:

{ "type": "object" }

Every required object must expose at least one of:

properties
oneOf
anyOf
allOf
typed additionalProperties

Add reusable Rust helper functions near tool_manifest() to generate inline schema fragments. Do not emit $ref in the MCP manifest.

Add helpers for:

human_id_schema
entity_id_schema
hash_schema
content_ref_schema
component_ref_schema
entity_schema
cognition_profile_schema
response_source_schema
cognition_workflow_schema
world_patch_schema
scenario_ref_schema
assemble_scenario_schema
fork_scenario_schema
pagination_schema

The emitted MCP schema must remain fully inline.


3. Put identifier grammar directly in schemas and descriptions

Do not tell consumers to read docs/terms.md.

Use this language in every relevant description:

Chukwa identifiers are exact and are not normalized. Use lowercase letters, digits, and single underscores only. Do not use uppercase letters, hyphens, spaces, leading underscores, trailing underscores, or doubled underscores. Entity IDs also allow dots for hierarchy, such as `plate.left_crumb`.

Use this schema for world slugs, scenario slugs, environment labels, profile labels, and other human IDs:

{
  "type": "string",
  "pattern": "^[a-z0-9]+(?:_[a-z0-9]+)*$",
  "minLength": 1,
  "maxLength": 64
}

Use this schema for entity IDs:

{
  "type": "string",
  "pattern": "^[a-z0-9]+(?:_[a-z0-9]+)*(?:\\.[a-z0-9]+(?:_[a-z0-9]+)*)*$",
  "minLength": 1,
  "maxLength": 128
}

Use this schema for content hashes:

{
  "type": "string",
  "pattern": "^[a-f0-9]{64}$"
}

4. Document and validate cognition profiles as workflow-backed only

Update put_cognition_profile, inline cognition profile refs in assemble_scenario, and cognition profile upserts in fork_scenario.

The public content shape is:

{
  "type": "object",
  "oneOf": [
    {
      "required": ["workflow_hash"],
      "properties": {
        "workflow_hash": {
          "type": "string",
          "pattern": "^[a-f0-9]{64}$",
          "description": "Hash returned by put_cognition_workflow."
        }
      },
      "additionalProperties": false
    },
    {
      "required": ["workflow"],
      "properties": {
        "workflow": {
          "type": "object",
          "description": "Inline cognition workflow. Chukwa validates and stores it automatically."
        }
      },
      "additionalProperties": false
    }
  ]
}

Enforce this at the MCP boundary.

Reject missing workflow data with:

BAD_ARG: cognition_profile content must contain exactly one of workflow_hash or workflow

Reject content that includes retired legacy cognition fields on the consumer surface:

perceive_system
intend_system
adjudicate_system
adjudication_schema
adjudication_retry_budget

Keep any required internal filler values inside the Rust bridge code only. Do not expose them, document them, or accept them as consumer input.

Update put_cognition_profile to return workflow_hash.

Update get_cognition_profile to return:

{
  "hash": "...",
  "found": true,
  "workflow_hash": "...",
  "content": {
    "workflow_hash": "..."
  }
}

5. Remove placeholder workflow acceptance from the consumer surface

Remove consumer-visible placeholder workflow behavior.

Specifically:

Also remove scenario_ref.data from create_world on the consumer surface.

create_world must accept only:

{ "scenario_ref": { "name": "..." }, "slug": "..." }

or:

{ "scenario_ref": { "hash": "..." }, "slug": "..." }

Reject scenario_ref.data with:

BAD_ARG: scenario_ref.data is not accepted by the consumer tool surface. Use assemble_scenario first, then create_world with scenario_ref.name or scenario_ref.hash.

This keeps placeholder-backed inline scenario creation out of the consumer tool surface.


6. Expose the entity shape directly

Update put_entity, inline entity refs in assemble_scenario, and entity replacement/upsert shapes in fork_scenario.

Expose this public entity shape:

{
  "type": "object",
  "required": ["id", "name", "environment"],
  "properties": {
    "id": {
      "type": "string",
      "pattern": "^[a-z0-9]+(?:_[a-z0-9]+)*(?:\\.[a-z0-9]+(?:_[a-z0-9]+)*)*$"
    },
    "name": {
      "type": "string"
    },
    "state": {
      "type": "string",
      "default": ""
    },
    "environment": {
      "type": "string",
      "pattern": "^[a-z0-9]+(?:_[a-z0-9]+)*$"
    },
    "kind": {
      "default": "prop",
      "oneOf": [
        {
          "const": "prop"
        },
        {
          "type": "object",
          "required": ["agent"],
          "properties": {
            "agent": {
              "type": "object",
              "required": ["goal", "memory", "cognition_profile"],
              "properties": {
                "goal": { "type": "string" },
                "memory": { "type": "string" },
                "cognition_profile": {
                  "type": "string",
                  "pattern": "^[a-z0-9]+(?:_[a-z0-9]+)*$",
                  "description": "Profile label from the scenario cognition_profiles map."
                }
              },
              "additionalProperties": false
            }
          },
          "additionalProperties": false
        }
      ]
    }
  },
  "additionalProperties": false
}

Add this explanation anywhere entities and scenarios are described:

cognition_profiles is the scenario map of available profile labels. An individual agent entity uses kind.agent.cognition_profile to point to one label in that map.

7. Expose and validate response sources

Update put_response_source to document and validate only the runtime-supported source kinds.

Accepted shapes:

{
  "kind": "llm_chat",
  "name": "default_llm",
  "model": "optional_model_hint"
}
{
  "kind": "http_json",
  "endpoint_url": "https://example.com/source"
}

Reject unsupported source kinds at put_response_source, not during run_turn.

Reject this:

{ "kind": "banana" }

with:

BAD_ARG: response source kind must be one of llm_chat or http_json

Reject this:

{ "kind": "http_json" }

with:

BAD_ARG: http_json response source requires endpoint_url

Implement validation in a shared helper used by both memory and Postgres scenario stores.


8. Expose the cognition workflow shape

Update put_cognition_workflow so the input schema explains the normal workflow authoring path.

Expose this public shape:

{
  "type": "object",
  "required": ["execution", "nodes", "apply"],
  "properties": {
    "execution": {
      "const": "linear"
    },
    "nodes": {
      "type": "array",
      "minItems": 1,
      "items": {
        "type": "object",
        "required": [
          "kind",
          "id",
          "source_ref",
          "max_generation_attempts",
          "max_tool_calls",
          "final_output",
          "final_schema_hash"
        ],
        "properties": {
          "kind": { "const": "llm_tool_loop" },
          "id": { "type": "string" },
          "source_ref": {
            "type": "string",
            "pattern": "^[a-f0-9]{64}$",
            "description": "Hash returned by put_response_source."
          },
          "max_generation_attempts": {
            "type": "integer",
            "minimum": 1
          },
          "max_tool_calls": {
            "type": "integer",
            "minimum": 0
          },
          "final_output": {
            "type": "string",
            "description": "Logical output name produced by this node. apply.from names this output."
          },
          "final_schema_hash": {
            "type": "string",
            "pattern": "^[a-f0-9]{64}$",
            "description": "Hash returned by put_json_schema."
          },
          "available_tools": {
            "type": "array",
            "default": []
          },
          "prompt_template": {
            "type": "object",
            "description": "Optional prompt template consumed by the workflow runner."
          }
        }
      }
    },
    "ambient_sources": {
      "type": "array",
      "default": [],
      "description": "Use an empty array for ordinary workflows."
    },
    "apply": {
      "type": "object",
      "required": ["from", "final_schema_hash"],
      "properties": {
        "from": {
          "type": "string",
          "description": "The node final_output to apply as the world patch."
        },
        "final_schema_hash": {
          "type": "string",
          "pattern": "^[a-f0-9]{64}$"
        }
      },
      "additionalProperties": false
    }
  }
}

Do not leave workflow or content as a bare object.


9. Include the WorldPatch contract in the existing manifest

Do not add a new get_chukwa_reference tool in this ticket.

Embed the WorldPatch schema in the put_json_schema and put_cognition_workflow descriptions/examples.

Expose this WorldPatch shape:

{
  "type": "object",
  "required": ["narration", "effects"],
  "properties": {
    "narration": {
      "type": "string"
    },
    "effects": {
      "type": "array",
      "items": {
        "oneOf": [
          {
            "type": "object",
            "required": ["op", "entity_id", "state"],
            "properties": {
              "op": { "const": "set_entity_state" },
              "entity_id": { "type": "string" },
              "state": { "type": "string" }
            },
            "additionalProperties": false
          },
          {
            "type": "object",
            "required": ["op", "entity_id", "content"],
            "properties": {
              "op": { "const": "append_entity_memory" },
              "entity_id": { "type": "string" },
              "content": { "type": "string" }
            },
            "additionalProperties": false
          },
          {
            "type": "object",
            "required": ["op", "environment_label", "content"],
            "properties": {
              "op": { "const": "set_environment_content" },
              "environment_label": { "type": "string" },
              "content": { "type": "string" }
            },
            "additionalProperties": false
          }
        ]
      }
    }
  },
  "additionalProperties": false
}

10. Make assemble_scenario self-documenting

Update assemble_scenario schema to expose the full callable shape.

Required fields:

scenario_slug
description
chronon_seconds
cognition_profiles
environments
entities

Document these rules:

cognition_profiles is a map from profile label to profile ref.
environments is a map from environment label to environment ref.
entities is an array of entity refs.
Agent entities refer to cognition profile labels through kind.agent.cognition_profile.
Entity environment fields refer to labels in the environments map.
Each component ref accepts exactly one of hash or content.

Add an executable example that creates:

one environment: plate
one profile: ant
one agent entity: ant
one prop entity: crumb

Do not use fake hashes or ... placeholders in examples.


11. Make fork_scenario self-documenting

Update fork_scenario schema to expose the full changes shape.

Document and support these change fields in the manifest:

scenario_slug
description
chronon_seconds
cognition_profile_upserts
cognition_profile_removals
environment_upserts
environment_removals
entities

Document primary_parent as a scenario ref using exactly one of:

name
hash

Document additional_parents as:

{
  "parent_hash": "64_hex_hash",
  "role": "relationship_label"
}

Add executable examples for:

  1. Fork a scenario and change only an environment.
  2. Fork a scenario and replace the entities list.

12. Fix audit pagination

Replace since with cursor in the consumer manifest for:

get_events
entity_history

Use this schema:

{
  "cursor": {
    "type": "string",
    "description": "Opaque cursor returned as next_cursor by the previous call. Omit it to start at the beginning. This is not a turn number or timestamp."
  }
}

Reject since on these consumer tools with:

BAD_ARG: use cursor pagination; since is not accepted

Keep response field:

next_cursor

No backward compatibility for since on the consumer MCP surface.


13. Fix list_turns description

Update the list_turns description to match the current handler response.

Do not advertise:

events_emitted
entities_touched

list_turns rows contain only:

turn
turn_ref
simulation_time
entity_count
state_hash
attempt_id
committed_at

Add this distinction to list_turns and list_attempts descriptions:

list_turns returns committed canonical turns only.
list_attempts returns run_turn attempts, including failed or interrupted attempts.

Do not add new TurnSummary fields in this ticket.


14. Expose delete_world.reason

Update the delete_world manifest schema to include:

{
  "reason": {
    "type": "string",
    "description": "Optional human-readable reason for the deletion request."
  }
}

Keep dry_run.

Remove consumer-visible references to code-navigator or operator tooling from delete_world.


15. Remove stale world_count from consumer-visible output

Remove world_count from:

list_scenarios description
list_scenarios response rows
get_scenario / stored_scenario_to_value response
consumer-facing tests

Do not emit a known-stale value.

Do not add a database migration for world_count in this ticket. Keep deeper store-model cleanup for a later cleanup ticket.


16. Include all NewComponents counters in assemble/fork responses

Update assemble_result_to_value so assemble_scenario and fork_scenario include all current NewComponents counters.

Include:

perceive_systems
intend_systems
adjudicate_systems
adjudication_schemas
cognition_profiles
cognition_workflows
json_schemas
response_sources
environments
entities

The modern workflow counters must appear in the response.


17. Add executable examples to the manifest

Add an examples field to every consumer tool entry.

Each example must include:

description
arguments

Use $token placeholders only for values produced by prior example steps, such as:

$world_patch_schema_hash
$response_source_hash
$cognition_workflow_hash
$cognition_profile_hash
$scenario_hash
$world_slug
$attempt_id
$next_cursor

Do not use literal "..." hashes.

Add a test harness that hydrates $token placeholders from prior example outputs and runs every manifest example through an in-memory MCP environment.

Include these three recipe sequences in examples:

Recipe A: run an existing scenario

list_scenarios
create_world
run_turn
get_turn_status
get_world

Recipe B: inspect history

list_turns
get_turn
diff_turns
get_events

Recipe C: author a workflow-backed scenario

put_json_schema
put_response_source
put_cognition_workflow
put_cognition_profile
put_environment
put_entity
assemble_scenario
create_world

Required tests

Add or update tests for all items below.

1. Consumer manifest has no required bare object schemas

Recursively inspect every consumer tool input schema.

Fail the test when a required object has only:

{ "type": "object" }

Allow required objects only when they include:

properties
oneOf
anyOf
allOf
typed additionalProperties

2. Consumer manifest contains no internal project-history language

Fail the test when any consumer-visible manifest string contains:

Phase
ticket
aggressive-cull
docs/terms.md
code-navigator
operator
legacy fixture

3. Pagination schema and handlers agree

Assert:

get_events exposes cursor
entity_history exposes cursor
get_events does not expose since
entity_history does not expose since
get_events rejects since
entity_history rejects since
responses include next_cursor

4. Cognition profile workflow chain works

Create a JSON schema.

Create a response source.

Create a cognition workflow.

Create a cognition profile from workflow_hash.

Assert put_cognition_profile returns:

hash
workflow_hash
new_components

Assert get_cognition_profile returns the same workflow_hash.

Assert missing workflow data fails with:

BAD_ARG

Assert retired legacy cognition fields fail on the consumer surface.

5. Placeholder workflow consumer path is closed

Assert put_cognition_profile with empty content fails.

Assert inline profile refs in assemble_scenario with missing workflow data fail.

Assert create_world with scenario_ref.data fails.

6. Response source validation fails early

Assert put_response_source rejects:

{ "kind": "banana" }

Assert put_response_source rejects:

{ "kind": "http_json" }

Assert put_response_source accepts:

{ "kind": "llm_chat" }

Assert put_response_source accepts:

{ "kind": "http_json", "endpoint_url": "https://example.com/source" }

7. world_count is gone from consumer-visible output

Assert world_count does not appear in:

list_scenarios response
get_scenario response
stored_scenario_to_value output
consumer manifest descriptions

8. delete_world.reason is exposed

Assert delete_world input schema contains:

reason

Assert handler receives and passes the reason through existing delete logic.

9. list_turns description matches response

Assert the manifest description does not contain:

events_emitted
entities_touched

Assert response rows contain only the documented fields.

10. Assemble/fork responses include all NewComponents counters

Assert assemble_scenario and fork_scenario responses include:

cognition_workflows
json_schemas
response_sources

alongside the existing counters.

11. Manifest examples execute

Run every consumer manifest example through the in-memory MCP handler.

Hydrate $token placeholders from prior example outputs.

Fail on missing token values, placeholder hashes, invalid arguments, or handler errors.


Definition of done

Proposed resolution

Completed the human review follow-up on the consumer MCP tool-surface ticket.

Implemented fixes:

Verification:

The worktree is clean against gitlab/main.

History (13 events)

Sign in as a human to drive this ticket from the page, or use the MCP tools.