{"id":46047624,"url":"https://github.com/andersonby/vv-agent","last_synced_at":"2026-05-31T09:00:46.292Z","repository":{"id":341217140,"uuid":"1169066486","full_name":"AndersonBY/vv-agent","owner":"AndersonBY","description":"A lightweight agent framework for production runtime. Cycle-based execution with pluggable LLM backends, tool dispatch, memory compression, and distributed scheduling.","archived":false,"fork":false,"pushed_at":"2026-05-31T04:04:43.000Z","size":1982,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-05-31T06:08:16.106Z","etag":null,"topics":["agent-framework"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/AndersonBY.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-02-28T06:01:15.000Z","updated_at":"2026-05-31T04:04:42.000Z","dependencies_parsed_at":"2026-05-31T09:00:43.102Z","dependency_job_id":null,"html_url":"https://github.com/AndersonBY/vv-agent","commit_stats":null,"previous_names":["andersonby/vv-agent"],"tags_count":37,"template":false,"template_full_name":null,"purl":"pkg:github/AndersonBY/vv-agent","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AndersonBY%2Fvv-agent","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AndersonBY%2Fvv-agent/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AndersonBY%2Fvv-agent/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AndersonBY%2Fvv-agent/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/AndersonBY","download_url":"https://codeload.github.com/AndersonBY/vv-agent/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AndersonBY%2Fvv-agent/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33725060,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-31T02:00:06.040Z","response_time":95,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["agent-framework"],"created_at":"2026-03-01T08:00:58.001Z","updated_at":"2026-05-31T09:00:46.275Z","avatar_url":"https://github.com/AndersonBY.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# vv-agent\n\n[中文文档](README_ZH.md)\n\nA lightweight agent framework extracted from VectorVein's production runtime. Cycle-based execution with pluggable LLM backends, tool dispatch, memory compression, and distributed scheduling.\n\n## Architecture\n\n```\nAgent / RunConfig / ModelSettings\n└── Runner\n    └── AgentRuntime\n        ├── CycleRunner          # single LLM turn: context -\u003e completion -\u003e tool calls\n        ├── ToolCallRunner       # tool dispatch, directive convergence\n        ├── RuntimeHookManager   # before/after hooks\n        ├── MemoryManager        # automatic history compression\n        └── ExecutionBackend     # inline, thread, or Celery scheduling\n```\n\nThe public SDK entry points are exported from `vv_agent`: `Agent`, `Runner`,\n`RunConfig`, `ModelSettings`, `function_tool`, `Session`, typed `RunEvent`\nobjects, and the interactive session API for desktop/runtime integrations.\nRuntime internals still use `RuntimeTask` (`AgentTask` during the remaining\ninternal migration), `AgentResult`, `Message`, `CycleRecord`, and `ToolCall`.\n\nTask completion is tool-driven: the agent calls `task_finish` or `ask_user` to signal terminal states. No implicit \"last message = answer\" heuristics.\n\n## Setup\n\n```bash\ncp local_settings.example.py local_settings.py\n# Fill in your API keys and endpoints in local_settings.py\n```\n\n```bash\nuv sync --dev\nuv run pytest\n```\n\n## Quick Start\n\n### CLI\n\n```bash\nuv run vv-agent --prompt \"Summarize this framework\" --backend moonshot --model kimi-k2.6\n\n# With per-cycle logging\nuv run vv-agent --prompt \"Summarize this framework\" --backend moonshot --model kimi-k2.6 --verbose\n```\n\nCLI flags: `--settings-file`, `--backend`, `--model`, `--verbose`.\n\n### Programmatic SDK\n\n```python\nfrom vv_agent import Agent, RunConfig, Runner, function_tool\n\n@function_tool\ndef read_order(order_id: str) -\u003e str:\n    \"\"\"Read order information.\"\"\"\n    return \"order details\"\n\nagent = Agent(\n    name=\"ops\",\n    instructions=\"Check facts first, then answer.\",\n    model=\"kimi-k2.6\",\n    tools=[read_order],\n)\n\nresult = Runner.run_sync(agent, \"Analyze order 123\", run_config=RunConfig(\n    default_backend=\"moonshot\",\n))\nprint(result.status, result.final_output)\n```\n\n`Agent.output_type` can coerce JSON final output into `dict`, `list`,\ndataclasses, or Pydantic-style models. Decorated tools may accept a leading\n`ToolContext` parameter; it is passed at invocation time and omitted from the\ntool JSON schema.\n\n### Streaming And Sessions\n\n`RunConfig.workspace` controls the workspace for a run. `RunConfig.session`\naccepts `MemorySession`, `SQLiteSession`, or `RedisSession` to persist message\nhistory across runs.\n\n```python\nfrom vv_agent import Agent, MemorySession, RunConfig, Runner\n\nagent = Agent(name=\"assistant\", instructions=\"Remember context.\", model=\"kimi-k2.6\")\nsession = MemorySession(\"thread-001\")\nconfig = RunConfig(\n    default_backend=\"moonshot\",\n    workspace=\"./workspace/thread-001\",\n    session=session,\n)\n\nRunner.run_sync(agent, \"Inspect the project\", run_config=config)\nfor event in Runner.stream_sync(agent, \"Continue and report progress\", run_config=config):\n    if event.type == \"assistant_delta\":\n        print(event.delta, end=\"\")\n```\n\nThe lower-level `AgentRuntime` API remains available for backend integrations\nthat need direct cycle-loop control.\n\nInstall Redis support with `uv sync --extra redis` or inject a Redis-compatible\nclient when constructing `RedisSession`.\n\n### Interactive Sessions\n\nUse `Runner` for one-shot runs, streamed runs, and conversation history managed\nby `RunConfig.session`. Use `InteractiveAgentClient` when the host application\nneeds a stateful, bidirectional runtime session with stable session ids,\nruntime listeners, queued steering prompts, follow-up turns, cancellation, and\nshared tool state.\n\n```python\nfrom pathlib import Path\n\nfrom vv_agent import (\n    AgentSessionOptions,\n    InteractiveAgentClient,\n    InteractiveAgentDefinition,\n)\nfrom vv_agent.runtime.backends import ThreadBackend\n\nclient = InteractiveAgentClient(\n    options=AgentSessionOptions(\n        settings_file=Path(\"local_settings.py\"),\n        default_backend=\"moonshot\",\n        workspace=Path(\"./workspace/thread-001\"),\n        execution_backend=ThreadBackend(max_workers=4),\n    )\n)\n\nsession = client.create_session(\n    session_id=\"thread-001\",\n    agent=InteractiveAgentDefinition(\n        description=\"Operate in the user's workspace and report progress.\",\n        model=\"kimi-k2.6\",\n        no_tool_policy=\"finish\",\n    ),\n)\nunsubscribe = session.subscribe(lambda event, payload: print(event, payload))\ntry:\n    run = session.prompt(\"Inspect the workspace\")\n    print(run.result.status, run.result.final_answer)\nfinally:\n    unsubscribe()\n```\n\nInteractive sessions are additive to the normal SDK facade; they do not\nreintroduce the old 0.1 `AgentSDKClient` or `AgentSDKOptions` names.\n\n### Agent As Tool, Handoff, And Policy\n\nUse `agent.as_tool()` when a child agent should return a result to the parent\nagent and let the parent continue. Use `handoff()` when control should transfer\nto the target agent and the target output should finish the run.\n\n```python\nfrom vv_agent import Agent, RunConfig, Runner, ToolPolicy, handoff\nfrom vv_agent.constants import TASK_FINISH_TOOL_NAME\n\nresearcher = Agent(name=\"researcher\", instructions=\"Collect facts.\", model=\"kimi-k2.6\")\nwriter = Agent(\n    name=\"writer\",\n    instructions=\"Write from research.\",\n    model=\"kimi-k2.6\",\n    tools=[researcher.as_tool(name=\"research\", description=\"Collect facts.\")],\n)\ntriage = Agent(\n    name=\"triage\",\n    instructions=\"Transfer writing tasks.\",\n    model=\"kimi-k2.6\",\n    handoffs=[handoff(agent=writer, description=\"Use for writing.\")],\n)\n\nresult = Runner.run_sync(\n    triage,\n    \"Write a short report.\",\n    run_config=RunConfig(\n        default_backend=\"moonshot\",\n        tool_policy=ToolPolicy(allowed_tools=[TASK_FINISH_TOOL_NAME, \"transfer_to_writer\"]),\n    ),\n)\n```\n\nTools can request approval with `@function_tool(needs_approval=True)`. By\ndefault the run enters `WAIT_USER` before the tool body is called and emits a\n`ToolApprovalRequestedEvent`. `ToolPolicy(approval=\"never\")` disables that\napproval gate for trusted runs.\n\n### Guardrails And Tracing\n\nInput guardrails run before the model provider is called. Output guardrails run\nafter a final output is available. Trace processors receive lightweight run and\ntool spans.\n\n```python\nfrom vv_agent import Agent, GuardrailResult, RunConfig, Runner, input_guardrail\n\n@input_guardrail\ndef reject_empty(ctx, input_text: str) -\u003e GuardrailResult:\n    del ctx\n    if not input_text.strip():\n        return GuardrailResult.block(\"input is required\")\n    return GuardrailResult.allow()\n\nagent = Agent(\n    name=\"assistant\",\n    instructions=\"Answer carefully.\",\n    model=\"kimi-k2.6\",\n    input_guardrails=[reject_empty],\n)\n\nresult = Runner.run_sync(\n    agent,\n    \"Summarize this project.\",\n    run_config=RunConfig(default_backend=\"moonshot\", tracing={\"workflow_name\": \"summary\"}),\n)\n```\n\n### Shell Runtime Configuration (Windows)\n\n`bash` runtime defaults are a **startup/session configuration**, not tool-call arguments.\n\n- Run defaults: pass `bash_shell`, `windows_shell_priority`, and `bash_env`\n  through `RunConfig.metadata`.\n- Per-agent defaults: put the same keys in `Agent.metadata`.\n- Recommended Windows priority: `[\"git-bash\", \"powershell\", \"cmd\"]`\n- On Windows, bash-tool child processes default `PYTHONUTF8=1` and `PYTHONIOENCODING=utf-8` unless already overridden via the parent environment or `bash_env`.\n- On Windows, bash-tool child processes are launched with hidden-console flags so GUI hosts can run `bash` / `powershell` commands without flashing a terminal window.\n- `Runner.run_sync(...)` and `Runner.stream_sync(...)` both inherit compiled\n  shell metadata.\n- The `bash` tool schema description includes a runtime shell hint (resolved shell kind + invocation prefix), so the model sees which shell command style is expected before calling the tool.\n- The runtime shell hint is frozen per task/session-run to keep tool schemas stable across cycles and preserve LLM prompt cache efficiency.\n- Runner/CLI-generated runtime tasks attach structured `system_prompt_sections`\n  metadata to the system message when prompt sections are available, so\n  Anthropic prompt-cache breakpoints can keep the stable prompt prefix hot while\n  treating current time and session-memory blocks as volatile.\n\n```python\nfrom vv_agent import Agent, RunConfig, Runner\n\nagent = Agent(\n    name=\"desktop\",\n    instructions=\"Desktop helper\",\n    model=\"kimi-k2.6\",\n    metadata={\"bash_env\": {\"HTTP_PROXY\": \"http://127.0.0.1:7890\"}},\n)\nresult = Runner.run_sync(\n    agent,\n    \"Check the workspace.\",\n    run_config=RunConfig(\n        default_backend=\"moonshot\",\n        metadata={\n            \"windows_shell_priority\": [\"git-bash\", \"powershell\", \"cmd\"],\n            \"bash_env\": {\"PIP_INDEX_URL\": \"https://pypi.tuna.tsinghua.edu.cn/simple\"},\n        },\n    ),\n)\n```\n\n## Execution Backends\n\nThe cycle loop is delegated to a pluggable `ExecutionBackend`.\n\n| Backend | Use case |\n|---------|----------|\n| `InlineBackend` | Default. Synchronous, single-process. |\n| `ThreadBackend` | Thread pool. Non-blocking `submit()` returns a `Future`. |\n| `CeleryBackend` | Distributed. Each cycle dispatched as an independent Celery task. |\n\n### CeleryBackend\n\nTwo modes:\n\n- **Inline fallback** (no `RuntimeRecipe`): cycles run in-process, same as `InlineBackend`.\n- **Distributed** (with `RuntimeRecipe`): each cycle is a Celery task. Workers rebuild the `AgentRuntime` from the recipe and load state from a shared `StateStore` (SQLite or Redis).\n\n```python\nfrom vv_agent.runtime.backends.celery import CeleryBackend, RuntimeRecipe, register_cycle_task\n\nregister_cycle_task(celery_app)\n\nrecipe = RuntimeRecipe(\n    settings_file=\"local_settings.py\",\n    backend=\"moonshot\",\n    model=\"kimi-k2.6\",\n    workspace=\"./workspace\",\n)\nbackend = CeleryBackend(celery_app=app, state_store=store, runtime_recipe=recipe)\nruntime = AgentRuntime(llm_client=llm, tool_registry=registry, execution_backend=backend)\n```\n\nInstall celery extras: `uv sync --extra celery`.\n\n### Cancellation and Streaming\n\n```python\nfrom vv_agent.runtime import CancellationToken, ExecutionContext\n\n# Cancel from another thread\ntoken = CancellationToken()\nctx = ExecutionContext(cancellation_token=token)\nresult = runtime.run(task, ctx=ctx)\n\ndef on_stream_event(event: dict) -\u003e None:\n    if event.get(\"event\") == \"assistant_delta\":\n        print(event.get(\"content_delta\", \"\"), end=\"\")\n\n\n# Stream LLM output events, including assistant deltas and tool progress\nctx = ExecutionContext(stream_callback=on_stream_event)\nresult = runtime.run(task, ctx=ctx)\n```\n\n### Runtime Log Payloads\n\n`tool_result` runtime events carry full tool output in `content` and any structured tool payload in `metadata` (no implicit truncation of `content`).\n`content_preview` and `assistant_preview` are still emitted for UI convenience.\n\nIf you need shorter previews for logs/transport, configure an explicit preview limit:\n\n```python\nfrom vv_agent import RunConfig\n\nconfig = RunConfig(\n    log_preview_chars=220,  # optional: enable preview truncation explicitly\n)\n```\n\n## Workspace Backends\n\nWorkspace file I/O is delegated to a pluggable `WorkspaceBackend` protocol. All built-in file tools (`read_file`, `write_file`, `list_files`, etc.) go through this abstraction.\n\n`list_files` includes built-in safety defaults for large workspaces:\n\n- Returns at most `500` paths per call by default (`max_results` can tune this, with hard cap).\n- Uses `ripgrep` (`rg`) for fast local traversal when available, with automatic fallback to Python walk.\n- `workspace_grep` also uses `rg` for local workspaces (with Python fallback), defaults to smart-case matching (lowercase patterns are case-insensitive; patterns with uppercase stay case-sensitive), and skips hidden/common dependency roots unless explicitly included.\n- `workspace_grep` returns model-facing grep text in `ToolExecutionResult.content`, while structured matches/counts live in `ToolExecutionResult.metadata`.\n- When listing from workspace root, common dependency/cache roots (for example `node_modules`, `.venv`, `.git`) are summarized instead of expanded.\n- You can still inspect those paths explicitly by setting `path` to that directory (or by setting `include_ignored=true`).\n- Supports `scan_limit` to stop early on very large trees; when triggered, response sets `count_is_estimate=true`.\n\n| Backend | Use case |\n|---------|----------|\n| `LocalWorkspaceBackend` | Default. Reads/writes to a local directory with path-escape protection. |\n| `MemoryWorkspaceBackend` | Pure in-memory dict storage. Great for testing and sandboxed runs. |\n| `S3WorkspaceBackend` | S3-compatible object storage (AWS S3, Aliyun OSS, MinIO, Cloudflare R2). |\n\n```python\nfrom vv_agent.workspace import LocalWorkspaceBackend, MemoryWorkspaceBackend\n\n# Explicit local backend\nruntime = AgentRuntime(\n    llm_client=llm,\n    tool_registry=registry,\n    workspace_backend=LocalWorkspaceBackend(Path(\"./workspace\")),\n)\n\n# In-memory backend for testing\nruntime = AgentRuntime(\n    llm_client=llm,\n    tool_registry=registry,\n    workspace_backend=MemoryWorkspaceBackend(),\n)\n```\n\n### S3WorkspaceBackend\n\nInstall the optional S3 dependency: `uv pip install 'vv-agent[s3]'`.\n\n```python\nfrom vv_agent.workspace import S3WorkspaceBackend\n\nbackend = S3WorkspaceBackend(\n    bucket=\"my-bucket\",\n    prefix=\"agent-workspace\",\n    endpoint_url=\"https://oss-cn-hangzhou.aliyuncs.com\",  # or None for AWS\n    aws_access_key_id=\"...\",\n    aws_secret_access_key=\"...\",\n    addressing_style=\"virtual\",  # \"path\" for MinIO\n)\n```\n\n### Custom Backend\n\nImplement the `WorkspaceBackend` protocol (8 methods) to plug in any storage:\n\n```python\nfrom vv_agent.workspace import WorkspaceBackend\n\nclass MyBackend:\n    def list_files(self, base: str, glob: str) -\u003e list[str]: ...\n    def read_text(self, path: str) -\u003e str: ...\n    def read_bytes(self, path: str) -\u003e bytes: ...\n    def write_text(self, path: str, content: str, *, append: bool = False) -\u003e int: ...\n    def file_info(self, path: str) -\u003e FileInfo | None: ...\n    def exists(self, path: str) -\u003e bool: ...\n    def is_file(self, path: str) -\u003e bool: ...\n    def mkdir(self, path: str) -\u003e None: ...\n```\n\n## Modules\n\n| Module | Description |\n|--------|-------------|\n| `vv_agent.runtime.AgentRuntime` | Top-level state machine (completed / wait_user / max_cycles / failed) |\n| `vv_agent.runtime.CycleRunner` | Single LLM turn and cycle record construction |\n| `vv_agent.runtime.ToolCallRunner` | Tool execution with directive convergence |\n| `vv_agent.runtime.RuntimeHookManager` | Hook dispatch (before/after LLM, tool call, memory compact) |\n| `vv_agent.runtime.StateStore` | Checkpoint persistence protocol (`InMemoryStateStore` / `SqliteStateStore` / `RedisStateStore`) |\n| `vv_agent.memory.MemoryManager` | Context compression when history exceeds threshold |\n| `vv_agent.workspace` | Pluggable file storage: `LocalWorkspaceBackend`, `MemoryWorkspaceBackend`, `S3WorkspaceBackend` |\n| `vv_agent.tools` | Built-in tools plus `function_tool`, `FunctionTool`, and structured tool outputs |\n| `vv_agent` | Public SDK: `Agent`, `Runner`, `RunConfig`, `ModelSettings`, tools, sessions, typed events |\n| `vv_agent.sdk` | Legacy migration internals; new user code should not use this as the main entry point |\n| `vv_agent.skills` | Agent Skills support (`SKILL.md` parsing, validation, unified normalization, prompt rendering with budget management, `activate_skill` tool) |\n| `vv_agent.llm.VVLlmClient` | Unified LLM interface via `vv-llm` (endpoint rotation, retry, streaming) |\n| `vv_agent.config` | Model/endpoint/key resolution from `local_settings.py` |\n\n## Memory Compaction\n\n`MemoryManager` now measures context size in tokens and compacts history when a model-derived auto-compaction threshold is exceeded.\n\n- Task-level knobs:\n  - `memory_compact_threshold` (default `128000`, legacy fallback only when token counting is unavailable)\n  - `memory_threshold_percentage` (warning threshold percentage, default `90`)\n- Compile mapping:\n  - `AgentCompiler` forwards stable agent/run metadata into `RuntimeTask`.\n  - Runtime-only compaction knobs remain metadata-backed until promoted into\n    stable public fields.\n- Token budget model:\n  - `effective_context_window = model_context_window - reserved_output_tokens`\n  - `autocompact_threshold = effective_context_window - autocompact_buffer_tokens`\n  - Defaults come from `vv-llm` model metadata when available, otherwise fall back to `200000 / 16000 / 13000`\n- Effective-length strategy (backend-aligned):\n  - If previous cycle token usage exists:\n    - `effective_length = previous_prompt_tokens + token_count(recent_tool_messages)`\n  - Otherwise fallback to:\n    - `vv_llm.chat_clients.utils.get_message_token_counts(...)`\n    - If tokenizer resolution fails, use a local CJK-aware estimate\n- Compaction pipeline:\n  1. Preemptive microcompact: clear old large tool results when usage crosses `microcompact_trigger_ratio`\n  2. Session Memory extraction: persist key facts before full summarization so they survive later compactions\n  3. Structural cleanup (stale tool calls, orphan tool messages, assistant-no-tool collapse, old tool result artifactization)\n  4. If still over threshold, generate a compressed memory summary that preserves original user messages, file operations, current work state, and resolved errors\n  5. If the provider still returns prompt-too-long, retry with forced compaction once, then progressively stronger emergency tail-dropping\n  6. After full compaction, re-inject relevant workspace files into `\u003cPost-Compaction File Context\u003e` under a bounded token budget\n- Session Memory behavior:\n  - Stored in `workspace/.memory/session/\u003csession-or-task-scope\u003e/session_memory.json` by default\n  - Scoped to the current session when `metadata.session_id` is present; otherwise scoped to the current `task_id`\n  - New sessions/tasks start without inherited Session Memory from previous sessions/tasks\n  - Injected into the first system message on every cycle as `\u003cSession Memory\u003e`\n  - Extraction reuses the configured memory summary backend/model\n  - Full compaction resets transcript tracking but preserves persisted memory entries\n  - Sub-tasks disable Session Memory by default to avoid parent/child memory-file contamination\n\n### Runtime metadata keys\n\nPass these via `Agent.metadata` or `RunConfig.metadata`; the compiler forwards\nthem into `RuntimeTask.metadata`:\n\n- `memory_keep_recent_messages`\n- `model_context_window`\n- `reserved_output_tokens`\n- `autocompact_buffer_tokens`\n- `microcompact_trigger_ratio`\n- `microcompact_keep_recent_cycles`\n- `microcompact_min_result_length`\n- `microcompact_compactable_tools`\n- `include_memory_warning`\n- `session_memory_enabled` / `enable_session_memory`\n- `session_memory_min_tokens`\n- `session_memory_max_tokens`\n- `session_memory_min_text_messages`\n- `session_memory_storage_dir`\n- `tool_result_compact_threshold`\n- `tool_result_keep_last`\n- `tool_result_excerpt_head`\n- `tool_result_excerpt_tail`\n- `tool_calls_keep_last`\n- `assistant_no_tool_keep_last`\n- `tool_result_artifact_dir`\n- `summary_event_limit`\n\n### Memory summary model selection priority\n\nPriority is strict:\n\n1. `RuntimeTask.metadata`\n   - `memory_summary_backend` / `memory_summary_model`\n   - aliases: `compress_memory_summary_backend` / `compress_memory_summary_model`\n   - aliases: `memory_compress_backend` / `memory_compress_model`\n2. `local_settings.py` constants\n   - `DEFAULT_USER_MEMORY_SUMMARIZE_BACKEND` / `DEFAULT_USER_MEMORY_SUMMARIZE_MODEL`\n   - aliases: `DEFAULT_MEMORY_SUMMARIZE_BACKEND` / `DEFAULT_MEMORY_SUMMARIZE_MODEL`\n   - aliases: `VV_AGENT_MEMORY_SUMMARY_BACKEND` / `VV_AGENT_MEMORY_SUMMARY_MODEL`\n3. Fallback\n   - runtime `default_backend` + current task `model`\n\n## Built-in Tools\n\n`list_files`, `file_info`, `read_file`, `write_file`, `file_str_replace`, `workspace_grep`, `compress_memory`, `todo_write`, `task_finish`, `ask_user`, `bash`, `read_image`, `create_sub_task`, `sub_task_status`.\n\nCustom tools can be registered via `ToolRegistry.register()`.\n\nThe `bash` tool supports two background paths:\n\n- Explicit background: pass `run_in_background=true`, receive a `session_id` immediately, then poll with `check_background_command`.\n- Timeout handoff: if a foreground command reaches `timeout`, it is moved into a background session instead of failing immediately. The tool returns a `session_id`, and the session emits terminal background-command events when that process completes, fails, or times out.\n\n## Sub-agents\n\nUse `Agent.as_tool()` when the parent agent should call a child agent and then\ncontinue. Use `handoff()` when the child agent should take over and finish the\nrun. Legacy `create_sub_task` tools still exist in the runtime while migration\ncontinues, but they are no longer the primary public SDK contract.\n\nEach delegated sub-task now runs in a real `AgentSession` (session id defaults to the sub-task id). Tool payloads include `session_id`, and runtime events include stable identifiers (`task_id` / `session_id`) so host apps can subscribe, persist, and stream sub-task progress independently, including `sub_agent_assistant_delta` and `sub_agent_tool_call_progress` events.\n\nBatch mode in `create_sub_task` dispatches valid sub-task items through the runtime execution backend's `parallel_map`, so synchronous batches run concurrently when the backend supports parallel execution.\n\nUse `sub_task_status` to query legacy runtime sub-task states, inspect\nlightweight progress snapshots (`detail_level=snapshot`), or send follow-up\nmessages to running/completed sub-tasks.\n\nBefore a completed sub-task is resumed, the runtime now sanitizes the saved session transcript: empty assistant turns, thinking-only turns, orphaned tool results, and unresolved tail tool calls are removed so the next follow-up prompt resumes from a coherent history.\n\nSub-task runtime metadata now includes `task_id`, `session_id`, and `browser_scope_key` for each sub-agent run, so session-scoped tools (for example, browser controllers) stay isolated across parallel sub-tasks.\n\nHost apps can interrupt a currently running sub-agent by calling `vv_agent.runtime.engine.steer_sub_agent_session(session_id=..., prompt=...)`.\n\nWhen a sub-agent uses a different model from the parent, the runtime needs `settings_file` and `default_backend` to resolve the LLM client.\n\n## Examples\n\nThe `examples/` directory now contains public SDK cookbook scripts plus a small\nset of lower-level runtime integration examples. See\n[`examples/README.md`](examples/README.md) for the full list.\n\n```bash\nuv run python examples/01_quick_start.py\nuv run python examples/24_workspace_backends.py\n```\n\n## Testing\n\n```bash\nuv run pytest                              # unit tests (no network)\nuv run ruff check .                        # lint\nuv run ty check                            # type check\n\nV_AGENT_RUN_LIVE_TESTS=1 uv run pytest -m live   # integration tests (needs real LLM)\n```\n\nEnvironment variables for live tests:\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `V_AGENT_LOCAL_SETTINGS` | `local_settings.py` | Settings file path |\n| `V_AGENT_LIVE_BACKEND` | `moonshot` | LLM backend |\n| `V_AGENT_LIVE_MODEL` | `kimi-k2.6` | Model name |\n| `V_AGENT_ENABLE_BASE64_KEY_DECODE` | - | Set `1` to enable base64 API key decoding |\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandersonby%2Fvv-agent","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fandersonby%2Fvv-agent","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandersonby%2Fvv-agent/lists"}