Deep Dive: QwenPaw's Dual-Layer Agent Architecture – Skill Base vs Tool Base
The article dissects QwenPaw's agent architecture, separating the skill base (asset management, versioning, prompt injection) from the tool base (secure execution runtime with registration, lookup, injection, and four-layer governance), detailing their end-to-end flow, boundary isolation, and core differences.
Part 1: From Zero to One – The Tool Base
1. Core Responsibilities of the Tool Base
The tool base is the runtime foundation that enables agents to actually perform work: running shell commands, reading/writing files, sending messages, calling MCP services. It answers four questions: how tools are registered, how agents find them, how tools enter the agent's runtime, and how they are executed safely. QwenPaw splits this into four links: registry for asset registration, workspace assembly for capability injection, LLM decision for tool selection, and triple-gate governance for execution safety.
2. Tool Registration: Turning Business Functions into Callable Agent Assets
2.1 Essence of Registration
Registration wraps a plain business function into a standard FunctionTool object that the agent can recognize, call, and govern. It records three things: the tool name (unique identifier for lookup), the schema (interface contract for the LLM describing inputs/outputs), and the func (real execution logic).
# Tool registration: wrap business function into FunctionTool and register
register_tool(name, func, schema):
tool = FunctionTool(name=name, func=func, schema=schema)
registry.register(name, tool) # tool name as unique ID
return tool2.2 Governance Shell at Registration Time
A key QwenPaw design: tools are not registered as bare functions but wrapped in a PolicyGuardedTool governance shell. Using dynamic type() construction, methods like check_permissions and __call__ are attached to the class, giving every registered tool automatic policy interception and approval-loop capabilities without the tool author writing any permission logic.
# PolicyGuardedTool dynamic construction: tool gains governance at registration
real_cls = type(
"PolicyGuardedTool",
(FunctionTool,),
{"check_permissions": _policy_tool_check_permissions,
"__call__": _policy_tool_call},
)2.3 Two Registration Forms
Function tool registration : built-in code tools, registered once at startup.
Skill pack registration : a skill is a SKILL.md + scripts/ directory stored in object storage (e.g., MinIO), loaded on demand into the runtime.
The former is static registration; the latter dynamic loading. Together they form the agent's capability asset pool.
3. Tool Lookup: How Agents Locate Available Tools
3.1 From LLM Decision to Registry Resolution
Lookup starts with the LLM's decision. During reasoning, the model picks a tool name and arguments based on task context. The base then resolves that name against the registry.
# Tool lookup: resolve by name, fail-closed on miss
def resolve_tool(name):
tool = registry.get(name)
if tool is None:
raise ToolNotFound(name) # unregistered tool -> immediate error
return toolA core detail is fail-closed : unknown tools are rejected at lookup (Phase 0 DENY with reason "unregistered tool"), not guessed.
3.2 Verifying Caller Eligibility
Lookup also validates the calling principal. The third gate, ChannelACL, enforces: whitelist → pass; blacklist → reject; unknown → pending admin approval. A bot added to a Telegram group must not gain full machine access; harmless commands from unauthorized users are still blocked.
# Channel admission: verify identity before lookup, default deny outside whitelist
def admit(channel, user):
if user in acl[channel].whitelist: return Pass()
if user in acl[channel].blacklist: return Reject()
acl[channel].pending.append(PendingEntry(user, first_msg))
return WaitForAdmin() # strangers enter pending queue4. Tool Injection: Assembling Capabilities into the Agent Runtime
Injection solves: after the agent process starts, how does it get tools? Two moments: startup-time bulk injection and runtime dynamic addition.
4.1 Startup Assembly: BootstrapFactory Uniform Injection
At process start, WorkspaceBootstrapFactory stuffs three categories into the Workspace object:
Built-in tool list : system-predefined functions (file I/O, shell exec, HTTP calls).
Lifecycle hooks : pre/post execution callbacks for audit, logging, timeout control.
Runtime modes : behavior config (e.g., allow network, enable sandbox).
These are built via build_kwargs(), returning a dict with keys tools, hooks, modes. Both Web (FastAPI) and ACP (CLI) entry points call the same build_kwargs(), guaranteeing identical tool sets across interfaces.
# Tool injection: factory uniformly mounts built-in tools and hooks
def build_kwargs(ws_cfg):
return {
"tools": self._builtin_tools(ws_cfg), # inject built-in tools
"hooks": self._builtin_hooks(), # inject lifecycle hooks
"modes": self._mode_registry() # inject runtime modes
}The workspace then pushes the tools descriptions into the system prompt so the LLM knows what's available.
4.2 Runtime Injection: Skill Hot Reload
After startup, new tools (e.g., a git skill) can be added without process restart. The flow:
Manager side : push skill directory ( SKILL.md + scripts/) via mc mirror to MinIO path agents/member01/skills/.
Worker side : update.py periodically checks MinIO skill directory ETag fingerprints; on change, calls refresh_and_enable_skills().
Activation : new skill loads into current process workspace; running tasks unaffected, new tasks immediately use new skill.
Key constraint: MinIO is the sole transport medium; Manager and Worker have no direct RPC. They agree via file paths and ETag fingerprints. MCP tool manifests travel the same pipe: mcporter.go generates mcporter.json to MinIO; worker pulls, parses MCP service addresses and tool lists, registers them into the tool registry.
# Skill injection: push skill dir to MinIO, worker hot-refreshes
mc mirror ./skills/git-skill minio/agents/member01/skills/ --overwrite --remove
mc stat minio/agents/member01/skills/git-skill/SKILL.md # contract validation
notify_worker member01 "Your Manager updated these workspace skills"Skill injection follows a "render → deliver/project → converge" three-stage link: governance plane renders skills into config assets, delivers to MinIO, execution plane pulls and converges. Control and execution planes decouple via MinIO file contracts, zero direct channels.
5. Tool Execution: Triple-Gate Defense in Depth
Execution is the tool base's core. Flow: LLM outputs tool_call → base intercepts → layered verification → real execution. QwenPaw places four independent verification gates between agent call and actual execution: Skill Health Check + Channel Admission + Policy Adjudication + Parameter Safety. Gates execute in that order; code paths fully independent, so a crash in one gate doesn't disable others.
5.1 Skill Health Check (Load Time)
Before a skill enters the workspace, SkillScanner statically scans the skill directory:
File count limit (500) and single file size limit (10 MB) to prevent resource exhaustion.
Malicious pattern detection (hardcoded secrets, dangerous syscalls).
Produces ScanResult{is_safe: bool}; unsafe skills rejected at load.
This moves the defense line from "call time" to "load time". Scanner itself has guardrails: max 500 files, 10 MB per file, scan process also protected against resource exhaustion.
5.2 Channel Admission ( ChannelACL )
When a user message enters, first verify sender identity:
Whitelist user → pass to LLM reasoning.
Blacklist user → reject.
Unknown user → pending queue, await admin approval.
This layer only checks "who sends", not message content or tool parameters.
5.3 Policy Adjudication ( PolicyGuardedTool )
After LLM decides a tool call, check_permissions() triggers before execution, returning one of four states: ALLOW → execute directly. DENY → refuse, return error to LLM. ASK → pause, wait for human approval. SANDBOX_FALLBACK → run in sandbox; sandbox violation upgrades to ASK.
Two critical boundary conditions:
If governor is None → universal DENY (governance component not initialized, fail-closed). OFF mode ≠ no isolation: OFF only skips "ask user" step but still attaches sandbox. No prompt ≠ no isolation.
Every adjudication result is logged via governor.audit().
# Tool execution: four-state adjudication + audit, governor missing -> fail-closed deny
def check_permissions(self):
if self.governor is None:
return Deny() # governance missing -> deny all
d = self.governor.assert_policy(self.spec())
self.governor.audit(self.spec(), d) # always audit
if d.is_off():
self.attach_sandbox() # OFF still attaches sandbox, only skips ask
return Allow()
return {"ALLOW": Allow, "DENY": Deny,
"SANDBOX": Sandbox, "ASK": Ask}[d]5.4 Parameter Safety ( ToolGuard )
Final gate: ToolGuard parameter safety engine checks "do this call's parameters look like an attack?" Runs four guardians serially:
SharedSafety : Catastrophic command classification (rm -rf /, mkfs, etc.) – cannot disable (always_run)
FilePath : Sensitive path access (/etc/passwd, ~/.ssh, etc.) – cannot disable (always_run)
RuleBased : YAML rules hot-updated, match custom danger patterns – can disable
ShellEvasion : Quote state machine detects command obfuscation – can disable
Two base strategies:
fail-open : single guardian exception doesn't cause overall deny; exception captured to result.failed, continue to next guardian. Only explicit "deny" from a guardian blocks.
always_run : SharedSafety and FilePath guardians ignore config switches; even if ops mistakenly disables ToolGuard, these baseline checks remain active. ShellEvasion logic: character-by-character parsing of shell command string, maintaining a quote state machine (single-quote, double-quote, no-quote). Inside single quotes no variable expansion → skip detection; inside double quotes $() still executes → retain detection. Variants like rm -rf /et<newline>c or r''m -rf are normalized to real commands before matching.
# Parameter safety: normalization + four guardians fail-open aggregation
def guard(self, tool_name, params):
params = normalize_continuations(params) # line-continuation normalization
result = Result()
for g in self.guardians:
try:
result.findings += g.guard(tool_name, params)
except Exception as e:
result.failed.append((g.name, str(e))) # single guardian exception doesn't break
return result6. End-to-End: Complete Journey of a Tool Call
Chaining the four links, a full tool call loop:
Skill Health Check: SkillScanner static scan before skill entry, malicious skills rejected.
Channel Admission: user message passes ChannelACL, whitelist pass, strangers pending.
LLM Decision: model picks target tool name + args from registry.
Policy Adjudication: PolicyGuardedTool four-state decision (allow/deny/ask/sandbox).
Sandbox Execution: SANDBOX_FALLBACK runs in sandbox, violations escalate to human approval.
Parameter Safety: ToolGuard four guardians check parameter shape, quote state machine deobfuscates shell attacks.
Real Execution: after all gates, execute in sandbox, return result, full audit trail persisted.
Part 2: From Zero to One – Relationship Between Tool Base and Skill Base
7. Core Positioning: Two Layers, Distinct Duties, Vertical Coupling
7.1 Tool Base (Runtime Execution Layer)
Positioning : atomic capability runtime, responsible for function registration, lookup, permission checks, sandbox execution – solves "can the function run safely?". Faces code functions; the agent's hands-and-feet runtime. Only cares about execution actions: wraps bare business functions into governed tool objects, provides registry addressing, identity ACL, parameter safety, sandbox isolation, call audit.
Core objects: FunctionTool, PolicyGuardedTool, global tool registry, ToolGuard, ChannelACL, Sandbox.
Solves four propositions: how to register, how to locate by name, how to inject into agent runtime, how to execute safely after multi-layer checks.
Essence: execution runtime, only governs call behavior, does not manage business semantics or documentation .
Key insight: the tool base itself produces no prompt material for the LLM . FunctionTool may carry a schema, but schema is only a registration-time input; the tool base never actively parses, extracts, or feeds schema to the LLM. Schema collection, multi-language adaptation, workspace filtering, prompt trimming – all belong to the upper skill base. Tool base only: receive function+schema, wrap governance shell, respond to calls, verify and execute.
7.2 Skill Base (Asset Management Layer)
Positioning : capability asset governance base, responsible for skill pack ingestion, version management, multi-tenant filtering, prompt injection – solves "can the capability be recognized by the model and distributed on demand?". A skill pack on disk:
skills/git-skill/
├── SKILL.md # skill description document, for model reading
├── scripts/
│ ├── git_ops.py # business function source
│ └── utils.py
└── manifest.json # version, dependencies, entry function nameSkill base does three things:
Read this directory from disk or MinIO.
Parse skill.md for text, parse manifest for function entry references.
Register these into global skill pool (in-memory dict).
It is a hosting pool for skill packs ( SKILL.md + scripts), the agent capability asset pool. Skill base does not execute code ; only hosts skill packs: pulls from market, file verification, atomic persistence, workspace isolation, parent-child agent permission trimming, injects skill descriptions into LLM context.
Core components: market source, registry, pool_service, atomic store, workspace assembler, effective skill set filter.
Core chain: skill pack pull → file signature verify → staging → atomic manifest write → pool registration → agent assembly filter → SKILL.md text injection into prompt.
Essence: asset management layer, only governs capability distribution and model decision material, does not participate in code execution. Skill base manages the full lifecycle of a skill pack: market download, version upgrade, local customization, conflict rename, cross-workspace reuse, child agent permission trimming, until disable/delete.
Skill base never calls scripts functions; it only reads directory metadata, SKILL.md docs, extracts script entry function references; actual function execution delegates downward to tool base.
7.3 Upper/Lower Relationship (Core Bottom Logic)
Skill base above, tool base below : skill base produces material, tool base consumes for execution. After skill base filters capabilities, it parses selected skill packs into in-memory objects, then hands the scripts business functions to lower tool base for registration, wrapping, execution. Skill base stores function reference paths + metadata schema + SKILL.md docs , not instantiated FunctionTool objects; instantiation happens only at agent assembly phase, then handed down to tool base for registration. Global skill pool never holds tool runtime objects, avoiding cross-agent instance pollution.
Skill base manages "capability entrance", tool base guards "execution exit" :
Skill base controls: which skills enter current agent (multi-tenant filter, parent-child intersection, blacklist trim).
Tool base controls: can the selected tool be called, are parameters malicious, does caller have permission.
Engineering reality: even if skill base allows a skill into agent's effective set, tool base can still refuse a specific call at runtime. Assembly-time permission ≠ runtime 100% pass; this is the dual-layer defense-in-depth design intent.
Skill base serves LLM decision side, tool base serves runtime execution side : skill base feeds SKILL.md into prompt so model knows "which capability to call"; tool base receives model's call instruction, completes addressing, verification, run, error write-back.
One-sentence summary: Skill base packages business capabilities into distributable, filterable, model-understandable assets; tool base wraps asset's business functions into controllable, isolated, safely executable runtime units. Skill base = capability "asset management platform"; tool base = capability "secure execution engine".
8. Underlying Principle: Two Chains' Connection Flow
8.1 Full Chain: Skill Base Outputs Tools, Tool Base Executes
Phase 1: Skill Base Completes Skill Asset Ingestion (Static Asset Phase) – occurs before agent instance creation; global init / user import actions, not tied to any agent session. Steps: (1) pull skill packs from built-in repo + four market sources; (2) enter registry: language variant parsing, file signature change detection, avoid duplicate loads; (3) enter store: write to staged temp dir for static health check ( SkillScanner), on pass atomic rename into manifest, prevent crash corruption; detect same-name conflicts, auto-generate version-suffixed names to avoid overwriting user config; (4) enter pool_service: upsert single entry, tag source (builtin/customized/external); preserve user config via preserve_from on upgrades; global lock prevents concurrent write corruption. Skill pack now safely in global skill pool as static asset.
Phase 2: Skill Base Completes Agent Assembly Filtering (Capability Trimming Phase) – occurs at AgentBuilder.build(), agent instance init; no LLM inference yet, pure in-memory static set ops. (1) resolve_effective_skills multi-layer filter: channel filter (by ChannelACL black/whitelist), dual-directory merge (global shared pool + workspace private copy, workspace copy higher priority for local override), parent-child intersection (child declared skills must intersect parent effective skills, blocking privilege escalation at source), source protection (protected skills forbidden for child inheritance, external mounted skills auto-validate directory validity). (2) Output: current agent's effective skill metadata set – not instantiated tool objects , just metadata records: skill name, source tag, SKILL.md path, scripts entry reference, schema. Real tool instantiation deferred to handoff to tool base. Benefit: same skill pool can generate multiple different agent effective metadata sets without prematurely consuming memory for tool instances.
Phase 3: Skill Base Hands Executable Functions to Tool Base (Two-Layer Junction) – key boundary. Metadata crosses from skill base to tool base, starting runtime-side instantiation. (1) Skill base loads selected skill packs: reads SKILL.md text, parses input schema, reads scripts business functions. (2) Hands business functions down to tool base. (3) Tool base runs tool registration: wraps bare function into FunctionTool, then wraps PolicyGuardedTool governance shell, auto-mounts permission check, audit, sandbox. (4) Finished tool instance registered into tool base's in-memory registry toolkit._qp_skills, completing two-layer data link.
# Code 1: Skill base delivers function ref to tool base (Phase 3 core action)
func_ref = skill_meta.scripts_entry # business function in scripts dir
schema = skill_meta.input_schema # JSON Schema from SKILL.md
tool_base = FunctionTool(func_ref, schema=schema) # Code 2: Bare function wrapped as FunctionTool (tool base registration step 1)
class FunctionTool:
def __init__(self, func_ref, schema):
self.func_ref = func_ref # business function reference
self.schema = schema # input constraint schema
self.name = func_ref.__name__ # tool name from function name # Code 3: Wrap with PolicyGuardedTool governance shell (tool base registration step 2)
guarded_tool = PolicyGuardedTool(
wrapped=tool_base, # bare tool being wrapped
acl=ChannelACL(), # channel admission check
sandbox=Sandbox(), # sandbox isolation
auditor=CallAuditor() # call audit
) # Code 4: Register into in-memory registry toolkit._qp_skills (two-layer connect)
toolkit._qp_skills[guarded_tool.name] = guarded_tool
# thereafter model's slash commands looked up here by tool nameDelivery is one-way downward. Tool base never writes back to skill pool manifest. Runtime call logs, session state stay in agent instance memory and session DB; no pollution of disk global skill pool assets. If multiple agents use same skill, each gets its own independent PolicyGuardedTool instances, isolated from each other.
Phase 4: Model Decision + Tool Base Execution (Call Loop) (1) Skill base injects SKILL.md text into model prompt; LLM decides, outputs slash call instruction (tool name + JSON args). (2) Instruction to tool base: step 1 – registry lookup by tool name, unregistered → fail-closed deny; step 2 – ChannelACL admission, verify caller identity; step 3 – PolicyGuardedTool four-stage policy adjudication (identity permission → input validation → risk detection → result audit); step 4 – ToolGuard four guardians parameter safety, quote state machine blocks shell obfuscation; step 5 – all checks pass, execute in sandbox; exceptions auto-caught, formatted error returned to model. (3) Call result written back to agent context, next LLM reasoning loop starts.
Even if skill base added skill to effective set, runtime still has multiple gates: user not in whitelist, parameter constructs shell obfuscation, governor down → all direct deny. Note: model only sees SKILL.md, never sees scripts internals; model outputs call instruction but completely unaware of underlying function implementation logic.
# Code 5: Runtime lookup by tool name and invoke (Phase 4 core action)
tool_name = parse_slash_command(output_text) # parse /skill_name
tool = toolkit._qp_skills.get(tool_name) # in-memory registry lookup
if not tool:
return "Tool not found" # fail-closed
result = tool.invoke(parsed_args) # run four-stage checks + sandbox8.2 Two-Layer Boundary Isolation Design (Core Engineering Constraints)
Semantic Boundary Isolation : Skill base only reads/writes SKILL.md, never reads scripts source; model only reads SKILL.md, never sees business code. Tool base only executes scripts functions, never reads skill docs; all decision basis passed from upper model. Benefit: "decision-execution decoupling" – modify business script without touching prompt doc; modify skill description without redeploying code. Engineering constraint: SKILL.md must be the sole structured entry for model to understand skill semantics . Script comments, docstrings not fed to model. If SKILL.md and script behavior diverge, "model cognition vs real execution mismatch" bug appears. Version iterations must keep SKILL.md and scripts in sync, enforced by skill base version sync mechanism.
Data Storage Isolation :
Skill base data: disk-persisted skill pool, manifest ledger, workspace config – global static assets, support cross-session persistence, version iteration, market distribution.
Tool base data: only in single agent instance memory – registry, tool instances, session call logs; destroyed with agent, no persistence of function runtime objects.
Cannot serialize runtime tool instances into manifest ledger. Manifest records only metadata: skill name, source tag, file path, user config (config/tags/automation); no FunctionTool object instances . On agent restart, tool instances entirely re-instantiated from metadata downward. Permission Protection Layered Isolation (Dual-Layer Defense in Depth) : Upper skill base: assembly-time static permission trimming (parent-child skill intersection, channel blacklist) – removes over-privileged capabilities before model decision. Lower tool base: runtime dynamic governance checks (PolicyGuardedTool four-stage + parameter safety + sandbox) – even if upper assembly logic has holes, nested calls cannot bypass execution-layer checks. Two-layer protection is "additive, not mutually exclusive". Cannot assume assembly trimming done → runtime checks can close; nor rely solely on runtime checks while dumping irrelevant skills into agent prompt context, causing token bloat and higher hallucination probability.
9. Core Differences Between Tool Base and Skill Base (Fundamental Distinctions)
9.1 Governance Object Differs
Skill base: governs skill asset packs – controls capability distribution, filtering, versioning, multi-tenant isolation; faces business capability packs.
Tool base: governs function call behavior – controls each call's identity, parameters, risk, execution environment; faces single function invocations.
Skill base manages "a capability pack" (one pack may contain multiple scripts, multiple function entries); tool base manages "a single function call" (each business function maps to one FunctionTool instance). One skill pack can register multiple tool objects – granularity not fully aligned.
9.2 Lifecycle Differs
Skill base: full lifecycle – download, ingest, update, distribute, until deletion from pool; long-term asset management.
Tool base: only single agent session lifecycle – tool instances live in memory during agent run, destroyed on session end; temporary runtime management.
When worker container restarts, all FunctionTool instances in memory destroyed; disk skill pool manifest kept intact by skill base. After restart, AgentBuilder re-reads disk metadata, re-instantiates tool base objects downward, business resumes. This underpins hot reload and long-task progress preservation.
9.3 Target Audience Differs
Skill base: faces LLM – provides capability description text, solves "does model know which capability to call?".
Tool base: faces local runtime – provides function execution ability, solves "can system safely execute this action?".
Without skill base, only tool base: can still run tool calls but all schemas/descriptions hardcoded, no market pull, multi-language variants, parent-child permission trimming; model's capability set static, new capability requires code change + restart. Without tool base, only skill base: just a pile of SKILL.md docs, zero execution ability; model reads docs but cannot do work.
9.4 Extension Form Differs
Skill base: supports dynamic hot load – pull skill packs from MinIO, update file signature, refresh effective immediately, no container restart, no long-task interruption.
Tool base: static registration (startup bulk load built-in functions) + dynamic registration (skill pack delivered → real-time tool instance registration) – balances startup init and runtime dynamic extension.
In cluster: AgentTeams Manager projects skill packs to MinIO – skill base cluster-side distribution logic ; worker nodes notified, local skill base updates, then notifies local tool base for dynamic registration. Cluster distribution is skill base extension, not tool base responsibility.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Tech Freedom Circle
Crazy Maker Circle (Tech Freedom Architecture Circle): a community of tech enthusiasts, experts, and high‑performance fans. Many top‑level masters, architects, and hobbyists have achieved tech freedom; another wave of go‑getters are hustling hard toward tech freedom.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
