Skip to content

English · Español

Lab 00 — Typed Tools

Goal: implement the four §A13 tools as plain Python functions with JSON-Schema descriptors. No MCP yet — just the tools.

Estimated time: 2–3 hours.

Prereq: Phase 12's §A13 conjugation truth table available as data/processed/conjugation_table.json (or wherever Phase 12 produced it).


What you produce

A directory experiments/31-typed-tools/ containing:

  • demo.py — instantiates each tool and prints a small smoke-test transcript.
  • results.json{tools_registered: 4, smoke_test_pass: true}.
  • manifest.json — per LYNX_CORTEX.md §5.

Plus, in src/miniagent/tools/:

  • __init__.py — registry + @register decorator.
  • base.pyTool dataclass.
  • conjugate.py — implements conjugate.
  • lookup_irregular_verb.py — implements lookup_irregular_verb.
  • lookup_spanish.py — implements lookup_spanish.
  • check_subject_verb_agreement.py — implements check_subject_verb_agreement.

TODOs

Block A — the Tool dataclass

In src/miniagent/tools/base.py:

  • Define a frozen dataclass:
    @dataclass(frozen=True)
    class Tool:
        name: str
        description: str
        input_schema: dict          # JSON-Schema
        fn: Callable[..., Any]
    
  • Define a ToolError exception type for tool-level errors (out-of-scope inputs, missing data).
  • Define a register decorator that takes name, description, and input_schema keyword arguments and adds the wrapped function to registered_tools in __init__.py.

Block B — conjugate

In src/miniagent/tools/conjugate.py:

  • @register(name="conjugate", description="Return the conjugated form of an English verb.", input_schema={...}) on a function:
    def conjugate(verb: str, tense: str, person: str) -> str:
        ...
    
  • Validate args against the §A13 enums; raise ToolError if out of scope.
  • Look up the form in data/processed/conjugation_table.json (or import it as a Python dict).
  • Return the conjugated string. E.g. conjugate("eat", "past_simple", "3sg") returns "ate".
  • Test exhaustively: 20 verbs × 5 tenses × 3 persons = 300 combinations. All must round-trip.

Block C — lookup_irregular_verb

In src/miniagent/tools/lookup_irregular_verb.py:

  • Signature: lookup_irregular_verb(verb: str) -> dict.
  • Returns {"infinitive": ..., "past_simple": ..., "past_participle": ..., "is_irregular": true} for the 8 irregular verbs (be, have, do, go, come, see, eat, write).
  • For a regular verb (one of the 12), returns {"is_irregular": false} — useful so the agent can ask "is this irregular?" before committing to a lookup path.
  • For an out-of-scope verb, raises ToolError.

Block D — lookup_spanish

In src/miniagent/tools/lookup_spanish.py:

  • Signature: lookup_spanish(english_form: str) -> str.
  • english_form is the conjugated form (e.g., "ate", not "eat").
  • Returns the canonical Spanish translation (peninsular Spanish, tú-form, simple past — see PHASE_31_PLAN.md §5 pitfall 6).
  • For an out-of-scope form, raises ToolError.

Block E — check_subject_verb_agreement

In src/miniagent/tools/check_subject_verb_agreement.py:

  • Signature: check_subject_verb_agreement(subject: str, verb_form: str) -> dict.
  • subject is a pronoun: "I", "you", "he", "she", "it". (Plural pronouns are out of §A13 scope; return a ToolError.)
  • verb_form is a conjugated English form.
  • Returns {"agrees": bool, "expected_form": str | None}. expected_form is filled when agrees=false, naming the conjugated form that would agree.

Example: check_subject_verb_agreement("he", "go") returns {"agrees": false, "expected_form": "goes"}. check_subject_verb_agreement("he", "goes") returns {"agrees": true, "expected_form": null}.

Block F — tests

In tests/test_miniagent_tools.py:

  • test_conjugate_round_trip — 300 cases, all match the truth table.
  • test_conjugate_out_of_scope_raisesconjugate("run", "past_simple", "3sg") raises ToolError.
  • test_lookup_irregular_verb_returns_principal_parts — all 8 irregulars present, with correct past_simple and past_participle.
  • test_lookup_irregular_verb_marks_regularslookup_irregular_verb("work") returns {"is_irregular": false}.
  • test_lookup_spanish_canonicallookup_spanish("ate") returns "comió" (or whatever the truth table specifies for 3sg past).
  • test_check_agreement_pos — agreeing cases return agrees=true.
  • test_check_agreement_neg_proposes_fix — disagreeing cases return agrees=false with the correct expected_form.
  • test_check_agreement_plural_pronoun_out_of_scopesubject="we" raises ToolError.

Block G — smoke demo

In experiments/31-typed-tools/demo.py:

  • Import registered_tools from src.miniagent.tools.
  • Print each tool's name and description.
  • Call each tool with one canonical input; print result.
  • Write results.json with {tools_registered, smoke_test_pass}.

Constraints

  • No MCP yet. This lab is pure Python. Lab 01 introduces the protocol layer.
  • Truth table is the contract. Phase 12 produced the data; tools read from it. Do not hand-code the conjugation grid inside the tool functions — that would let it drift from the corpus.
  • No pydantic requirement. If Borja wants to add it for argument validation, fine; if not, stdlib dict validation is enough.
  • No I/O side effects. Tools read constants and compute. They do not write files, hit network, or call external services.

Stop conditions

Done when:

  1. All Block F tests pass.
  2. demo.py prints a clean transcript and writes results.json.
  3. Each tool's input_schema validates with jsonschema.Draft7Validator(schema).check_schema() (sanity-check the schema is well-formed).

Pitfalls

  • Hand-coded vs table-driven. Easy to start typing the conjugation grid inside conjugate. Resist; pull from the truth table.
  • Spanish translation variants. Pick one canonical form per English form and document the choice. Variants (vos vs tú, peninsular vs Latin American) are explicit non-features.
  • Pronoun normalization. "He" vs "he" vs "He.". Tools should lowercase + strip punctuation before comparing.
  • is_irregular: false is not an error. lookup_irregular_verb("work") is a successful call returning a useful answer — not an exception.
  • expected_form of null when agreeing. Don't return an empty string; return None. Distinguish "no fix needed" from "fix is the empty string".

When to consult solutions/

After all Block F tests pass. The solution at solutions/00-typed-tools-ref.md (written at phase open) will cross-check the schema shapes; minor differences are fine but the test-truth-table mapping must match.


Next lab: lab/01-mcp-server.md.