Agents Describe Themselves
v2.6 gives built agents one immutable, inspectable self-model.AgentProfile records
the resolved identity, driver, capabilities, tools, hooks, self-knowledge, and public
metadata after the builder has finished composition.
Host applications can inspect that model without reconstructing it from runtime
internals:
UseSelfDescription installs the opt-in describe_self tool. It returns the
same resolved description as the host API, including itself, and can narrow the
result to tools, capabilities, or hooks. The tool list describes what is
installed on the loop; execution-time policy hooks may still block individual
calls.
Derived System Prompts and Opt-In Self-Knowledge
UseSystemPrompt now derives tool names, tool guidance, capability order, and agent
identity from the final AgentProfile. Hidden tools remain callable but are
omitted from the generated prompt. The generated block is owned, idempotent
across steps, and rebound when a copied loop changes its tools, driver, or
lifecycle interceptor.
UseSelfKnowledge publishes the
curated Agents documentation paths and topic routes only when the packaged resources
are readable and, by default, the resolved agent has read, read_file, or a
tool tagged with both file and read. Generic read-only introspection tools do
not satisfy the gate. It does not install a tool or search outside the installed
package. The
source-to-resource manifest, sync verifier, and archive check keep the shipped
21-document mirror aligned with the authored documentation.
Composer Manifest Discovery
Trusted packages can declare lazily resolved capabilities and tools in Composer metadata:CapabilityDiscovery::discover() is an explicit application bootstrap step. It
reads Composer 2 runtime metadata, validates declarations, and registers lazy
factories without instantiating contributed classes. Root-package mappings
override vendor mappings. Resolution errors identify the package, registered
name, class, wrong interface, missing class, or unsupported constructor.
Discovery executes classes supplied by installed dependencies. Enable it only
for a trusted dependency set; configured entries should continue to use
application-owned registry factories.
Agent Definition Round-Trip
AgentDefinitionSerializer writes one canonical definition as Markdown, YAML, or
JSON, preserving meaningful fields across parse-serialize-parse round trips.
AgentDefinitionValidator and DefinitionLoopFactory share capability and tool
reference rules, so persisted definitions are checked against the same
registries used to build a loop.
FileAgentDefinitionStore accepts a dedicated writable root, derives filenames
from validated agent names, writes atomically, rejects traversal, and refuses
overwrite unless replacement is explicit. UseAgentDefinitions installs read-only
list_agents and read_agent tools by default. Supplying a store explicitly adds
write_agent; successful writes refresh the registry but do not mutate an already
running loop.
Inference Hooks and Mutation Contracts
ToolCallingDriver now exposes the concrete provider boundary through two lifecycle
phases:
BeforeInferenceRequestruns after request construction and can replace the request that the provider receives.AfterInferenceResponseruns after provider execution and can replace the response before events, tool handling, and step archival consume it.
ReActDriver, capability-owned inference, and
custom drivers continue unchanged unless they implement
CanAcceptLifecycleInterceptor and emit these phases themselves.
Each HookTrigger now declares its intended mutable HookContext fields.
Default execution remains permissive for compatibility.
withContractDiagnostics() emits a
HookContractViolated event while preserving the returned context; strict()
emits the event and throws HookContractViolationException on the first
disallowed field.
Use strict mode in tests or controlled deployments before enabling it broadly.
blockToolExecution() is the clearer tool-blocking method.
withToolExecutionBlocked() remains as a compatibility alias.
Tell Reference CLI
Thecognesy/instructor-tell package is now a small, non-interactive reference
frontend for the public Agents APIs rather than a direct bare inference
streamer. Agent definitions are resolved into loops through
DefinitionLoopFactory and AgentBuilder; named conversations execute through
SessionRuntime.
Tell adopts the Agent eXperience Interface at the process
boundary. Running tell without a prompt returns live workspace content,
counts, and next actions. Default structured output uses TOON, list commands
offer compact schemas through --fields, empty results are explicit, and long
session content requires --full.
The prompt command is the default entry point, alongside six inspection and
management subcommands:
tell tell "..." form remains available. Put a prompt that matches
a subcommand name after --, for example tell -- agents. The prompt command
accepts --agent, --connection, --model, --dsn, --session, --dir,
--tools, --max-steps, and --output=toon|text|json|events. --connection
resolves a Polyglot LLM preset; --model overrides only its model, while --dsn
supplies the complete inline configuration.
TOON mode is the default and writes terminal status, answer, step count, usage,
and errors. Text mode writes only the final response. JSON emits the same
terminal data, while events mode writes event-complete NDJSON. Progress stays on
stderr; -v adds step and tool progress. Errors are structured stdout data,
unknown flags include valid flags and contextual help, and stderr never carries
the machine result. Exit codes are 0 for success, 1 when execution does not
produce a successful response, and 2 for invalid usage. Symfony supplies the
global quiet, verbosity, interaction, and ANSI options.
Named sessions persist and continue prior messages. Without --session, Tell performs
no session-storage I/O.
Tell now uses one explicit local home, configurable with TELL_HOME and
defaulting to ~/.tell. Authored configuration and user agent definitions live
under config/, mutable session state under runtime/sessions/, and append-only
JSONL traces under logs/. Stateless turns write one dated file per execution;
named conversations use separate, stable session files so parallel sessions do
not share a target. Appends use an exclusive file lock.
Tell can optionally manage provider credentials in
~/.tell/config/credentials.env; raw keys do not belong in tell.json.
Resolution is deterministic: process environment, then the selected workspace’s
.env, then the Tell credential store. tell auth status reports only safe
source provenance, tell auth set <provider> --stdin writes atomically with
private permissions, and tell auth remove <provider> mutates only Tell-owned
state. Ambient values are never copied automatically. Missing remote credentials
fail before inference, while local Ollama-style connections need no key.
User connection overlays live under ~/.tell/config/connections/; project
presets override user files, and user files override bundled presets. All
${VARIABLE} interpolation uses an application-injected Config secret resolver,
which avoids global environment mutation and admits a future OS-keychain source
without changing preset files.
Execution tracing is enabled by default without changing stdout. Trace records
contain event identity, timestamps, agent/session/workspace context, and
sanitized event data. Prompt, tool argument/result, state, and context payloads
are omitted unless observability.includePayloads is enabled in
config/tell.json; credential-shaped fields remain redacted and strings remain
bounded. Configuration typos fail before inference, while an unavailable trace
sink degrades without failing the requested turn. Running bare tell reports
the effective observability settings and non-secret operational storage paths;
it deliberately omits the credential-store path.
tell describe and tell tools render the same resolved public profile returned
by the built loop, preventing the CLI from drifting from runtime behavior.
tell describe --prompt --json runs the same prompt-composition lifecycle used
for execution, so its systemPrompt includes both the definition persona and
derived tool guidance.
Tell does not install ambient editor/session hooks or inject a Tell-usage skill
into agents. AXI is intentionally limited to its shell contract: Tell has no
separate workspace state to inject, and self-integration would create a
recursive Tell-teaches-Tell layer.
tell planes makes the operational boundary explicit without creating three
services or parallel code trees. Relative to the local Tell runtime, agent turns
are data-plane work; resolved profile and tool policy are control-plane work;
agent-definition inventory, credential management, and session lifecycle are
management-plane work.
The compact view lists the command responsibilities. --full adds state
ownership, typed cross-plane inputs/outputs, authority, and degraded behavior,
including the honest limitation that Tell resolves a fresh immutable profile per
invocation rather than reusing a persisted last-known-good control snapshot.
The data-plane description also names its append-only trace authority and its
fail-open behavior when external observability storage is unavailable.
Capability-Focused Examples
The example catalogue now verifies the new seams at their owning abstraction level:D01_Agents/AgentDescribeinspects a directly assembledAgentLoopwithout using builder capabilities.D02_AgentBuilder/AgentDerivedPrompt,AgentDescribe, andAgentSelfKnowledgedemonstrate profile-derived prompts and opt-in self-inspection throughAgentBuildercapabilities.D03_AgentTemplates/AgentDefinitionRoundTrip,AgentDefinitionTools, andComposerCapabilityDiscoverydemonstrate definition persistence, definition tools, and trusted Composer manifest discovery.
Faster, Swappable Tokenizer
Token counting now goes through a contract instead of a hardcoded implementation.Cognesy\Utils\Tokenization\Contracts\CanCountTokens covers counting; the wider
CanTokenizeText adds encode() and encoding() for drivers that can produce
token IDs.
Two drivers ship under Cognesy\Utils\Tokenization\Drivers:
TiktokenDriverwrapsyethee/tiktokenand is the new default. It reaches modern encodings viaTiktokenDriver::forEncoding('cl100k_base')orTiktokenDriver::forModel('gpt-4o'), and on a 20 KB text it counts in about 0.7 ms against roughly 17 ms for the previous implementation, in a third of the memory.Gpt3TokenizerDriverwraps the bundledgioni06/gpt3-tokenizer(r50k_base) and is now the fallback. It needs no network.
Tokenizer keeps its tokenCount() signature and gains default(),
setDefault(), and reset(). It also memoizes the default instance - it
previously rebuilt a ~25 MB vocabulary on every single call.
Cognesy\Utils\Tokenization\TokenizerResolver decides which driver
Tokenizer::default() uses. It prefers tiktoken with o200k_base and falls back
to the bundled tokenizer when that vocabulary cannot be obtained, so every
component that counts tokens - SplitMessages, MoveMessagesToBuffer,
SummarizeBuffer, the agents summarization hooks, and UseSummarization - gets
the faster path without any code change. Each of them still accepts an explicit
CanCountTokens when a specific encoding is required.
Those components now resolve the fallback on their first count rather than in
their constructors. Wiring one into a pipeline previously loaded a vocabulary
whether or not it ever counted anything - about 73 ms and 22 MB per process with
the new default. The trade-off is that a component built without an explicit
tokenizer follows later Tokenizer::setDefault() calls instead of pinning
whatever was current when it was constructed.
INSTRUCTOR_TOKENIZER overrides the choice without touching code: auto
(default), gpt3, tiktoken, or tiktoken:<encoding>. The explicit forms do
not fall back - silently receiving a different tokenizer would mean silently
receiving different counts.
Package and Dependency Changes
cognesy/agentsdirectly requirescomposer-runtime-apifor installed-package discovery andsymfony/yamlfor definition serialization.cognesy/agentsships its curated documentation mirror under package resources; authoreddocs/remain excluded from distribution archives.cognesy/tellnow directly requirescognesy/agentsandsymfony/console. Its old direct messages dependency and unrelated demo/config prompt resources were removed.cognesy/instructor-utilsnow requiresyethee/tiktoken(about 100 KB, withsymfony/service-contractsas its only dependency) so the faster tokenizer is the default for every package that counts tokens. The bundledgioni06/gpt3-tokenizeris still required as the offline fallback.CodingAgentPromptis deprecated but preserves its 2.5 static prompt for this minor release. UseUseSystemPromptfor tool and guideline sections derived from the built agent.
Compatibility and Breaking Changes
Most Agents additions are opt-in: existing builders do not gain system prompts, self-knowledge, self-description tools, manifest discovery, writable definition tools, or strict hook enforcement unless the application enables them. Review these concrete compatibility points:HookTriggerhas two new enum cases. Update exhaustivematchexpressions. Hooks registered withHookTriggers::all()now also receiveBeforeInferenceRequestandAfterInferenceResponsewhen used withToolCallingDriver; hooks that assume one of the older context shapes should register only the triggers they handle.CodingAgentPromptremains callable with its 2.5 static tool list, guidelines, and optionaldocumentation_pathsection, but is deprecated. New code should useUseSystemPromptand addUseSelfKnowledgeseparately when installed package documentation should be discoverable.- Token counts change.
Tokenizer::tokenCount()now answers witho200k_baseinstead ofr50k_base, generally producing fewer tokens for the same text, so summarization and buffer thresholds trigger later than they did in 2.5. The first count in a process also resolves a vocabulary over the network unless one is already cached. SetINSTRUCTOR_TOKENIZER=gpt3to keep the 2.5 numbers and stay offline, ortiktoken:r50k_baseto keep the numbers and gain the speed. - Tell still accepts its original prompt/connection/model/DSN invocation, but
it now executes an agent loop and uses agent-oriented output and exit
semantics. Code that
subclassed the former non-final
TellCommandor imported removed internal demo resources must migrate to the public command/factory and packaged definition surfaces.
Upgrade Notes
- Replace
CodingAgentPrompttool guidance withUseCodingToolsplusUseSystemPrompt. AddUseSelfKnowledgeseparately when the agent should know how to route into the installed Agents documentation. - Audit exhaustive
HookTriggermatches and hooks registered withHookTriggers::all(). Prefer the smallestHookTriggerscollection each hook actually supports. - For provider-boundary interception, use
BeforeInferenceRequestandAfterInferenceResponsewithToolCallingDriver. Do not assume custom or ReAct drivers emit those phases. - Introduce hook mutation enforcement progressively: observe
HookContractViolatedevents withAgentBuilder::withHookContractDiagnostics()before enablingAgentBuilder::withStrictHookContracts(). Directly assembledHookStackinstances can still usewithContractDiagnostics()andstrict(). - Treat Composer capability discovery as a supply-chain decision. Call
CapabilityDiscovery::discover()only after deciding which installed packages are trusted to contribute executable classes. - Supply
FileAgentDefinitionStoreonly when an agent should receive definition write authority. Keep the default read-only capability otherwise. - Tell now defaults to TOON. Scripts that require JSON should choose
--output=json; event consumers should choose--output=events. Treat exits1and2as structured execution and usage failures respectively. The old stopped-without-answer exit3is now exit1. - Revisit persisted token budgets and summarization thresholds against the new
default encoding. Air-gapped deployments should either set
INSTRUCTOR_TOKENIZER=gpt3or pre-populateTIKTOKEN_CACHE_DIR. Leaving both unset still works - the fallback catches it - but every process pays one failed download attempt first, which on a blackholed network waits out PHP’sdefault_socket_timeoutrather than failing immediately.