@kedi.query¶
Docstring Programs¶
A query function's docstring must start with a standalone kedi line:
import kedi
@kedi.query
def title_for(topic: str) -> str:
"""kedi
>> Short documentation title for <topic>: [title: str].
= `title`
"""
...
Kedi removes the header, dedents the docstring, and compiles the remaining text as the body of an internal procedure. An absent docstring, a non-standalone header, or an empty program fails when the decorator is evaluated.
The docstring therefore contains procedure-body statements, not another
top-level @procedure declaration. Procedure-valid directives such as
> model:, > system:, and > use: may appear in the body.
Function Signatures¶
The Python signature defines call binding:
@kedi.query
def explain(topic: str, audience: str = "developers") -> str:
"""kedi
>> Explanation of <topic> for <audience>: [answer: str].
= `answer`
"""
...
Python's normal positional, keyword, and default-argument rules apply before Kedi starts. Every bound argument becomes a runtime value under its parameter name.
Ignored Python Bodies¶
The decorated body is metadata-only:
@kedi.query
def classify(text: str) -> str:
"""kedi
>> <text> is a [label: Literal["bug", "feature", "question"]].
= `label`
"""
raise AssertionError("never executed")
The assertion never runs. Prefer ... to make this ownership obvious.
If the original callable is declared async def, Kedi returns an async wrapper
and runs blocking model work in a worker thread. The original async body still
does not run.
Argument Binding¶
Arguments enter the environment as native Python values. Use:
<name>to render a value into prompt text;<name>when explicit inline Python rendering is useful;- bare
nameinside Python code to consume the native object.
Configured env values have higher precedence than call arguments. Do not
reuse a parameter name in local env unless overriding caller input is
intentional.
Native Return Values¶
The Kedi return controls the runtime value:
from pydantic import BaseModel
@kedi.type
class Review(BaseModel):
decision: str
summary: str
@kedi.query
def review(text: str) -> Review:
"""kedi
>> Review of <text>: [result: Review].
= `result`
"""
...
= `result` returns the native Review. = <result> renders it and
returns a string. The Python return annotation helps callers and type checkers,
but Kedi does not coerce a mismatched DSL result to that annotation.
Dynamic Output Types¶
Type expressions can read Python arguments:
from typing import TypeVar
T = TypeVar("T")
@kedi.query
def extract(text: str, output_type: type[T]) -> T:
"""kedi
>> Structured representation of <text>: [result: `output_type`].
= `result`
"""
...
output_type is a native Python class in the runtime environment. The adapter
must support that output schema.
Per-Query Backend Overrides¶
Framework adapters use adapter=; process-backed harnesses use agent=:
@kedi.query(
adapter="pydantic",
model="openai:gpt-4o-mini",
system="Return only the requested result.",
effort="low",
settings={"temperature": 0.1},
)
def extract_name(text: str) -> str:
"""kedi
>> The person in <text> is named [name].
= `name`
"""
...
Use agent="codex", agent="claude", or agent="acp" for harnesses. Passing
both adapter and agent is an error. Adapter instances are accepted when
they expose matching kind and shortname metadata.
Tools, Environment, and Skills¶
@kedi.query(
tools=[search_docs],
env={"audience": "maintainers"},
skills=True,
)
def answer(question: str) -> str:
"""kedi
> use: search_docs
>> Use the documentation when needed. Return [answer: str] for <question>.
= `answer`
"""
...
Local tools override configured tools with the same registered name. Skills
add the explicit list_skills and read_skill tools; they do not inject every
skill file into the prompt.
An argument may not have the same name as any registered tool in scope. Kedi rejects that collision rather than silently making the tool uncallable.
Approval and MCP Overrides¶
Queries accept approval= and mcp_servers=:
from kedi import McpServerSpec
@kedi.query(
approval="allow",
mcp_servers=[
McpServerSpec(
transport="http",
url="http://127.0.0.1:8000/mcp",
)
],
)
def investigate(question: str) -> str:
"""kedi
>> Investigate <question> with available tools and return [answer: str].
= `answer`
"""
...
The selected adapter must support the requested capabilities. Local MCP servers are appended to configured servers.
Response Caching¶
cache=True opts this callable into the in-memory response cache:
@kedi.query(cache=True)
def stable_summary(text: str) -> str:
"""kedi
>> Summary of <text>: [summary: str].
= `summary`
"""
...
The key includes source, callable identity, arguments, configured and local environment, backend identity, model/profile settings, tools, MCP, approval, and skills. Concurrent identical misses coalesce into one producer call. Failures are not cached.
Cached results are deep-copied on storage and retrieval when possible. Values that cannot be deep-copied fall back to by-reference behavior.