SimpleLLMFunc Framework Development
When to use this skill
- Use this skill when the task changes the framework itself, not just an app built on it.
- Typical triggers: editing
SimpleLLMFunc/,tests/,docs/,spec/, built-in tools, runtime primitives, decorator semantics, event-stream behavior, provider adapters, TUI utilities, or contributor docs.
Core development philosophy
- LLM is Function: preserve the framework's function-first design — LLM calls are indistinguishable from Python function calls.
- Prompt as Code: docstrings are system prompts. Code and prompts are never separated.
- Context-Centric: each provider request is compiled from invocation configuration, a base transcript/history, and runtime transcript patches.
ContextMutationis an internal typed patch protocol, not the whole context source of truth. No component may directly modify the live ReAct transcript. - Keep public behavior explicit and typed.
- Favor small, composable modules over hidden orchestration.
- Prefer explicit boundaries between pure transforms, state mutation, and orchestration side effects. Recent selfref/ReAct work depends on keeping those lines sharp.
- Follow repo-grounded conventions instead of generic framework habits.
- When docs and code disagree, source and tests are the final authority.
Default implementation workflow
- Read the relevant docs, tests, and source before changing behavior.
- Map the affected layer: decorator, compile boundary, ReAct runtime, tool system, runtime primitives, interface, hooks, or docs/spec.
- Write or update tests first for the behavior you are changing.
- Make the smallest coherent implementation change.
- Run targeted tests, then broader tests if the change touches shared behavior.
- Update docs/examples/specs when user-facing behavior or architecture changed.
TDD and validation loop
- Start with a failing or missing test that captures the new behavior.
- Use red -> green -> refactor.
- Prefer focused unit tests in the mirrored
tests/location. - Add or update a runnable example when the feature is user-facing.
- If behavior changes affect docs or spec, update them in the same change.
Project map
L1. Decorator Layer (llm_decorator/)
Public entry points and invocation contract building.
llm_function_decorator.py:@llm_function— returns anLLMFunctioncallable instance;await instance(...)gives typed result,instance.stream(...)yieldsReactOutputllm_chat_decorator.py:@llm_chat— returns anLLMChatcallable instance; calling it yields aReactOutputasync stream and provides a stable SelfRef agent identityinvocation_spec.py:InvocationSpec,PromptContract,TranscriptSeedinvocation_builder.py:build_function_invocation_spec(),build_chat_invocation_spec()prompt_contract.py: prompt templates, XML Schema generation, type descriptionssignature.py:parse_function_signature(), trace_id, log contextutils/tools.py:process_tools(),collect_tool_prompt_specs()selfref_sync.py: legacy compatibility shim aroundSelfRefSession; activellm_chatbinding/session orchestration lives inLLMChatplusruntime/selfref/session.py
L2+L3. Compile Boundary + ReAct Runtime (base/)
Mutation-driven context evolution and event-only ReAct loop.
Core loop modules:
react_loop.py:run_react_loop()— main async generator, event-onlyllm_call.py:execute_single_llm_phase()— single LLM call, streaming/non-streamingtool_scheduler.py:schedule_tool_batch()— concurrent asyncio.Task executionreact_hooks.py:ReActHookExecutionContext, hook lifecycle execution
Compile pipeline:
compile_pipeline.py: single compile entry —compile_invocation_turn(),reduce_turn_context(),convert_to_llm_request()context_compile.py:apply_mutations(),compile_context()— mutation apply enginellm_input_render.py:render_llm_input_messages()— ephemeral system prompt rendering
Type contracts (base/types/):
source.py:CompileSource,DataFromAgentConfig,DataFromSelfRefcontext.py:ContextState,CompiledContextcompile.py:ReducedTurnContext,CompiledTurnContextmutation.py:ContextMutationunion type (10 variants)react.py:ReactLoopStatellm.py:SingleLLMCallResultscheduler.py:ToolSchedulerResult
Message and tool sub-modules:
messages/: assistant message building, usage extraction, multimodal content, validationtool_call/: extraction, execution, streaming state, validationtype_resolve/: type description, XML round-trip, multimodal detectionpost_process.py: response → typed result (XML → Pydantic)
Runtime Primitive System (runtime/)
primitives.py:PrimitiveRegistry,PrimitivePack,PrimitiveCallContext,@primitive()worker_proxy.py:WorkerRuntimeProxy,WorkerRuntimeNamespace,PrimitiveTransportselfref/state.py:SelfReferencepublic facade, lifecycle, compatibility exportsselfref/store.py: durable history/source storeselfref/active_turn.py: active memory key, fork context, runtime toolkit, template params, active ReAct state contextvarsselfref/mutations.py: pending compaction/context/destructive mutation queuesselfref/memory_api.py:self_reference.memory[...]proxy/handle APIselfref/context_memory.py: context snapshots, experience CRUD, compaction commit, direct memory editing APIselfref/agent_binding.py: recursive agent callable binding stateselfref/fork_manager.py: fork/spawn/gather lifecycle and result materializationselfref/fork_utils.py: fork helper functions and compatibility constantsselfref/session.py:SelfRefSession— invocation-scoped plugin implementing ReAct hooksselfref/context_ops.py:parse_context_messages(),build_context_messages_from_state_data(),canonicalize_context_messages()selfref/primitives.py: selfref primitives (guide, context.inspect/remember/forget/compact, fork.is_bound/spawn/gather_all)
Built-in Tools (builtin/)
pyrepl.py:PyReplpublic facadepyrepl_execution.py: execute/reset worker protocol orchestrationpyrepl_worker_client.py: subprocess and multiprocessing queue lifecyclepyrepl_worker_mixin.py: facade-compatible worker lifecycle wrapperspyrepl_primitive_host.py: runtime backend, primitive registry, primitive RPC, fork clone integrationpyrepl_tools.py: execute/reset tool creation and output formattingpyrepl_audit.py: audit log writerpyrepl_input_bridge.py: process-wide input request/reply bridgepyrepl_input_mixin.py: PyRepl input submission APIfile_tools.py:FileToolset— workspace-scoped file tools with stale-write protection
Event Stream (hooks/)
events.py: 14ReActEventsubtypesstream.py:ReactOutput,ResponseYield,EventYield, type guardsevent_bus.py:EventBus— event ingress with origin metadataevent_emitter.py:ToolEventEmitter,NoOpEventEmitter
Interface Layer (interface/)
llm_interface.py:LLM_Interfaceabstract base classopenai_compatible.py:OpenAICompatibleadapteropenai_responses_compatible.py:OpenAIResponsesCompatibleadapterkey_pool.py:APIKeyPool— multi-key rotationtoken_bucket.py: token-bucket rate limiting
Infrastructure
logger/: structured logging, trace_id, async context managerobservability/: Langfuse trace/span integrationtype/: multimodal types (Text,ImgUrl,ImgPath) and canonical chat input (UserChatMessage)utils/tui/: Textual TUI integration
Tests (tests/)
Mirror of behavior and architecture; often the fastest place to infer conventions.
Naming and style rules
- File names:
snake_case. - Functions:
snake_case. - Classes:
PascalCase. - Constants:
UPPER_SNAKE_CASE. - Public APIs should carry type annotations.
- Public user-facing callables should have useful docstrings.
- Follow the repo's existing formatting and PEP 8 style.
Framework-specific development rules
Core rule: runtime transcript patch boundary
LLM-visible messages are compiled from invocation config, a base transcript/history, and runtime patches:
- Producers (LLM call, tool exec, selfref hooks) emit
ContextMutationobjects for runtime transcript edits. react_loopcollects mutations before each compile boundary.compile_context()applies mutations in order viaapply_mutations().- The compile pipeline combines that patched transcript with invocation config and SelfRef source data before rendering provider messages.
Do not introduce code that directly mutates ContextState.messages outside of apply_mutations(). Do not let tools or primitives write directly to the live transcript. Also do not describe mutations as the source of all context: docstrings, template params, tool schemas, and initial history are separate compile inputs.
SelfRef rules
SelfReference(runtime/selfref/state.py) is the public facade for the durable backend; do not re-expand it into a god object.SelfRefSession(runtime/selfref/session.py) is the invocation-scoped plugin that implements ReAct hooks (collect_context_mutations,finalize, etc.).- Keep pure context parsing/rendering in
runtime/selfref/context_ops.py. - Keep durable history/source storage in
runtime/selfref/store.py. - Keep active turn contextvars and active ReAct state lookup in
runtime/selfref/active_turn.py. - Keep pending mutation/compaction queues in
runtime/selfref/mutations.py. - Keep context memory operations, experience CRUD, compaction commit, and direct memory editing in
runtime/selfref/context_memory.py. - Keep fork/spawn/gather lifecycle behavior in
runtime/selfref/fork_manager.pyand pure-ish helpers/constants inruntime/selfref/fork_utils.py. LLMChatis the stable callable agent instance bound intoSelfReference; keep per-call mutable state inside invocation locals /SelfRefSession, not on shared instance fields.llm_decorator/selfref_sync.pyis a legacy compatibility shim; do not move new lifecycle logic there unless intentionally reviving that layer.- SelfRef can only affect the system through: source snapshot, pending intents → mutations, finalize side effects.
runtime.selfref.fork.spawn(...)children inherit the pre-fork context snapshot. Do not reintroduce the parent's pending assistant tool-call message into child-visible history.
ReAct runtime rules
- The core is event-only:
run_react_loop()always yieldsReactOutput. - There is no
enable_eventparameter; event mode is the only mode at the core level. - New terminal behavior should flow through the shared finalize path so
before_finalizestays consistent across event, abort, and max-tool-cap exits. - Abort is mutation-producing behavior:
AssistantTruncatedMutationfor LLM abort,ToolCancelledMutationfor tool abort. - Do not add dual-mode (event/non-event) logic inside
react_loop.py,llm_call.py, ortool_scheduler.py.
Decorator rules
@llm_functionreturnsLLMFunction;@llm_chatreturnsLLMChat. Preserve function-like metadata (__wrapped__,__name__,__doc__,__annotations__,__signature__) when changing them.- Multimodal input policy:
llm_functionshould use explicitImgUrl/ImgPathtyped parameters, whilellm_chatshould prefer one OpenAI-compatibleUserChatMessageuser-message object. - Do not reintroduce closure-only wrapper implementations for SelfRef binding; SelfRef should bind the stable
LLMChatinstance. - There is no
enable_eventorreturn_modedecorator parameter;llm_chatalways yieldsReactOutput, andllm_function.stream(...)yieldsReactOutput. - Prefer
async deffor decorated public patterns and tool implementations. - Keep docstring-parsed contracts in sync with behavior. This matters for
@tooland runtime primitives. - Runtime primitive docstrings must include
Best Practices; registration fails without them. - Preserve history semantics in
llm_chat:historyandchat_historyare special names. - Preserve structured output parsing behavior unless the task explicitly changes it.
Provider adapter rules
- Keep wire-format differences in the adapter layer under
SimpleLLMFunc/interface/. - Do not leak Responses-specific request/stream contracts into
ReActor decorator code unless the public framework contract is intentionally changing. OpenAIResponsesCompatibleshould remain a first-class adapter, not a special case hidden insideReAct. System prompts map to Responsesinstructions, and Responses-specific reasoning/tool-stream handling belongs in the adapter.
Test rules
- Treat tests as executable API documentation for subtle cases like self-reference, event mode, and provider compatibility.
- Add architecture tests (invocation isolation, compile single-entry, history authority) when changing the invocation/compile/finalize pipeline.
Documentation and spec rules
- Update
mintlify_docs/when user-facing behavior changes. - Update
spec/when module responsibilities, architecture map, or repo-wide guidance changes. - Keep examples runnable and aligned with current behavior.
- Use progressive disclosure in skills and docs: concise guidance in the main file, details in reference docs.
- Keep
provider.jsonformat docs and.env/ environment-variable docs aligned with actual loader and observability behavior. - Keep the packaged
skills/directory and thesimplellmfunc-skillexport CLI aligned so installed users can export the current skill contents correctly. - When Responses adapter behavior or selfref fork behavior changes, update packaged
skills/docs in the same change, not onlymintlify_docs/. - Treat
AGENTS.mdas a feedback-loop artifact: when recurring agent mistakes reveal missing environmental guidance, update the file so the fix lives in the system instead of only in maintainer memory.
Read these reference docs as needed
- Architecture and contributor map:
reference/project-map.md - TDD, tests, and validation expectations:
reference/testing-and-tdd.md - Coding conventions and naming rules:
reference/style-and-spec.md - Framework-specific gotchas:
reference/framework-gotchas.md - PyRepl architecture, IPC, primitive RPC, event streaming, and image artifact capture:
reference/pyrepl-architecture.md - Docs and examples workflow:
reference/docs-and-examples.md - Maintainer workflow notes:
reference/AGENT.md - Contributor guide:
reference/contributing.md - Mirrored repo spec:
reference/spec/project-map.md,reference/spec/overall-spec.md,reference/spec/primitive-dev-api-plan.md,reference/spec/meta.md - Developer examples:
examples/add_runtime_primitive_pattern.py,examples/test_first_decorator_change.md,examples/update_docs_checklist.md
Source: NiJingzhe/SimpleLLMFunc — distributed by TomeVault.