ffwf-tau-agent-core 0.9.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ffwf_tau_agent_core-0.9.2/LICENSE +21 -0
- ffwf_tau_agent_core-0.9.2/PKG-INFO +190 -0
- ffwf_tau_agent_core-0.9.2/README.md +164 -0
- ffwf_tau_agent_core-0.9.2/pyproject.toml +71 -0
- ffwf_tau_agent_core-0.9.2/setup.cfg +4 -0
- ffwf_tau_agent_core-0.9.2/src/ffwf_tau_agent_core.egg-info/PKG-INFO +190 -0
- ffwf_tau_agent_core-0.9.2/src/ffwf_tau_agent_core.egg-info/SOURCES.txt +169 -0
- ffwf_tau_agent_core-0.9.2/src/ffwf_tau_agent_core.egg-info/dependency_links.txt +1 -0
- ffwf_tau_agent_core-0.9.2/src/ffwf_tau_agent_core.egg-info/requires.txt +14 -0
- ffwf_tau_agent_core-0.9.2/src/ffwf_tau_agent_core.egg-info/top_level.txt +1 -0
- ffwf_tau_agent_core-0.9.2/src/tau_agent_core/__init__.py +171 -0
- ffwf_tau_agent_core-0.9.2/src/tau_agent_core/agent_loop.py +1418 -0
- ffwf_tau_agent_core-0.9.2/src/tau_agent_core/agent_loop_types.py +85 -0
- ffwf_tau_agent_core-0.9.2/src/tau_agent_core/agent_session.py +3983 -0
- ffwf_tau_agent_core-0.9.2/src/tau_agent_core/agent_session_runtime.py +522 -0
- ffwf_tau_agent_core-0.9.2/src/tau_agent_core/commands.py +180 -0
- ffwf_tau_agent_core-0.9.2/src/tau_agent_core/compaction.py +898 -0
- ffwf_tau_agent_core-0.9.2/src/tau_agent_core/compaction_policy.py +453 -0
- ffwf_tau_agent_core-0.9.2/src/tau_agent_core/compaction_utils.py +187 -0
- ffwf_tau_agent_core-0.9.2/src/tau_agent_core/completion.py +116 -0
- ffwf_tau_agent_core-0.9.2/src/tau_agent_core/conversation_tree.py +683 -0
- ffwf_tau_agent_core-0.9.2/src/tau_agent_core/event_projection.py +250 -0
- ffwf_tau_agent_core-0.9.2/src/tau_agent_core/events.py +318 -0
- ffwf_tau_agent_core-0.9.2/src/tau_agent_core/export.py +411 -0
- ffwf_tau_agent_core-0.9.2/src/tau_agent_core/extension_types.py +2407 -0
- ffwf_tau_agent_core-0.9.2/src/tau_agent_core/extensions/__init__.py +35 -0
- ffwf_tau_agent_core-0.9.2/src/tau_agent_core/extensions/registry.py +215 -0
- ffwf_tau_agent_core-0.9.2/src/tau_agent_core/extensions/runner.py +813 -0
- ffwf_tau_agent_core-0.9.2/src/tau_agent_core/extensions_builtin/__init__.py +10 -0
- ffwf_tau_agent_core-0.9.2/src/tau_agent_core/extensions_builtin/nats_bus.py +856 -0
- ffwf_tau_agent_core-0.9.2/src/tau_agent_core/latency.py +360 -0
- ffwf_tau_agent_core-0.9.2/src/tau_agent_core/messages.py +115 -0
- ffwf_tau_agent_core-0.9.2/src/tau_agent_core/rpc/__init__.py +67 -0
- ffwf_tau_agent_core-0.9.2/src/tau_agent_core/rpc/capabilities.py +201 -0
- ffwf_tau_agent_core-0.9.2/src/tau_agent_core/rpc/commands.py +3740 -0
- ffwf_tau_agent_core-0.9.2/src/tau_agent_core/rpc/dialect.py +248 -0
- ffwf_tau_agent_core-0.9.2/src/tau_agent_core/rpc/handler.py +1254 -0
- ffwf_tau_agent_core-0.9.2/src/tau_agent_core/rpc/protocol_doc.py +427 -0
- ffwf_tau_agent_core-0.9.2/src/tau_agent_core/rpc/transport.py +819 -0
- ffwf_tau_agent_core-0.9.2/src/tau_agent_core/rpc/wire_events.py +143 -0
- ffwf_tau_agent_core-0.9.2/src/tau_agent_core/rpc_event_schema.py +350 -0
- ffwf_tau_agent_core-0.9.2/src/tau_agent_core/run_manifest.py +181 -0
- ffwf_tau_agent_core-0.9.2/src/tau_agent_core/sdk.py +867 -0
- ffwf_tau_agent_core-0.9.2/src/tau_agent_core/session.py +222 -0
- ffwf_tau_agent_core-0.9.2/src/tau_agent_core/session_catalog.py +261 -0
- ffwf_tau_agent_core-0.9.2/src/tau_agent_core/session_log.py +516 -0
- ffwf_tau_agent_core-0.9.2/src/tau_agent_core/session_manager.py +775 -0
- ffwf_tau_agent_core-0.9.2/src/tau_agent_core/settings.py +111 -0
- ffwf_tau_agent_core-0.9.2/src/tau_agent_core/submission.py +314 -0
- ffwf_tau_agent_core-0.9.2/src/tau_agent_core/testing/__init__.py +37 -0
- ffwf_tau_agent_core-0.9.2/src/tau_agent_core/testing/session_catalog_contract.py +351 -0
- ffwf_tau_agent_core-0.9.2/src/tau_agent_core/testing/session_log_contract.py +724 -0
- ffwf_tau_agent_core-0.9.2/src/tau_agent_core/tools/__init__.py +85 -0
- ffwf_tau_agent_core-0.9.2/src/tau_agent_core/tools/base.py +224 -0
- ffwf_tau_agent_core-0.9.2/src/tau_agent_core/tools/bash.py +333 -0
- ffwf_tau_agent_core-0.9.2/src/tau_agent_core/tools/edit.py +222 -0
- ffwf_tau_agent_core-0.9.2/src/tau_agent_core/tools/find.py +180 -0
- ffwf_tau_agent_core-0.9.2/src/tau_agent_core/tools/grep.py +191 -0
- ffwf_tau_agent_core-0.9.2/src/tau_agent_core/tools/ls.py +171 -0
- ffwf_tau_agent_core-0.9.2/src/tau_agent_core/tools/read.py +211 -0
- ffwf_tau_agent_core-0.9.2/src/tau_agent_core/tools/write.py +159 -0
- ffwf_tau_agent_core-0.9.2/src/tau_agent_core/usage.py +71 -0
- ffwf_tau_agent_core-0.9.2/tests/test_30_permission_gate.py +255 -0
- ffwf_tau_agent_core-0.9.2/tests/test_31_protected_paths.py +228 -0
- ffwf_tau_agent_core-0.9.2/tests/test_32_pirate.py +169 -0
- ffwf_tau_agent_core-0.9.2/tests/test_33_claude_rules.py +211 -0
- ffwf_tau_agent_core-0.9.2/tests/test_34_desktop_notify.py +152 -0
- ffwf_tau_agent_core-0.9.2/tests/test_35_auto_commit_on_exit.py +242 -0
- ffwf_tau_agent_core-0.9.2/tests/test_36_file_trigger.py +239 -0
- ffwf_tau_agent_core-0.9.2/tests/test_37_inline_bash.py +226 -0
- ffwf_tau_agent_core-0.9.2/tests/test_43_budget_ledger.py +421 -0
- ffwf_tau_agent_core-0.9.2/tests/test_53_router_ledger.py +444 -0
- ffwf_tau_agent_core-0.9.2/tests/test_60_retrieval_review.py +200 -0
- ffwf_tau_agent_core-0.9.2/tests/test_70_telemetry.py +203 -0
- ffwf_tau_agent_core-0.9.2/tests/test_agent_loop.py +2282 -0
- ffwf_tau_agent_core-0.9.2/tests/test_agent_loop_types.py +313 -0
- ffwf_tau_agent_core-0.9.2/tests/test_agent_session.py +1915 -0
- ffwf_tau_agent_core-0.9.2/tests/test_agent_session_runtime.py +604 -0
- ffwf_tau_agent_core-0.9.2/tests/test_agent_spec_preview.py +256 -0
- ffwf_tau_agent_core-0.9.2/tests/test_agent_spec_provenance.py +231 -0
- ffwf_tau_agent_core-0.9.2/tests/test_api_on_hook_bridge.py +251 -0
- ffwf_tau_agent_core-0.9.2/tests/test_append_entry_durable.py +173 -0
- ffwf_tau_agent_core-0.9.2/tests/test_append_only_compaction.py +170 -0
- ffwf_tau_agent_core-0.9.2/tests/test_before_agent_start_hook.py +187 -0
- ffwf_tau_agent_core-0.9.2/tests/test_blocked_call_render.py +304 -0
- ffwf_tau_agent_core-0.9.2/tests/test_branch_view.py +197 -0
- ffwf_tau_agent_core-0.9.2/tests/test_budget.py +467 -0
- ffwf_tau_agent_core-0.9.2/tests/test_compaction_engine.py +1125 -0
- ffwf_tau_agent_core-0.9.2/tests/test_compaction_policy.py +943 -0
- ffwf_tau_agent_core-0.9.2/tests/test_compaction_utils.py +109 -0
- ffwf_tau_agent_core-0.9.2/tests/test_context_hook_removed.py +154 -0
- ffwf_tau_agent_core-0.9.2/tests/test_contract_inmemory.py +11 -0
- ffwf_tau_agent_core-0.9.2/tests/test_conversation_tree.py +396 -0
- ffwf_tau_agent_core-0.9.2/tests/test_ctx_complete.py +235 -0
- ffwf_tau_agent_core-0.9.2/tests/test_ctx_session_ops.py +252 -0
- ffwf_tau_agent_core-0.9.2/tests/test_deferral_queue.py +351 -0
- ffwf_tau_agent_core-0.9.2/tests/test_error_handling.py +622 -0
- ffwf_tau_agent_core-0.9.2/tests/test_event_bus.py +833 -0
- ffwf_tau_agent_core-0.9.2/tests/test_event_projection.py +492 -0
- ffwf_tau_agent_core-0.9.2/tests/test_events.py +395 -0
- ffwf_tau_agent_core-0.9.2/tests/test_examples_contract.py +302 -0
- ffwf_tau_agent_core-0.9.2/tests/test_export.py +394 -0
- ffwf_tau_agent_core-0.9.2/tests/test_extension_config.py +164 -0
- ffwf_tau_agent_core-0.9.2/tests/test_extension_error_visibility.py +240 -0
- ffwf_tau_agent_core-0.9.2/tests/test_extension_loader.py +338 -0
- ffwf_tau_agent_core-0.9.2/tests/test_extension_manifest.py +297 -0
- ffwf_tau_agent_core-0.9.2/tests/test_extension_record_sink.py +161 -0
- ffwf_tau_agent_core-0.9.2/tests/test_extension_registry.py +525 -0
- ffwf_tau_agent_core-0.9.2/tests/test_extension_runner.py +386 -0
- ffwf_tau_agent_core-0.9.2/tests/test_extension_submit.py +279 -0
- ffwf_tau_agent_core-0.9.2/tests/test_extension_tools_loop.py +278 -0
- ffwf_tau_agent_core-0.9.2/tests/test_extension_types.py +1486 -0
- ffwf_tau_agent_core-0.9.2/tests/test_extensions_summary.py +113 -0
- ffwf_tau_agent_core-0.9.2/tests/test_fork_admission.py +186 -0
- ffwf_tau_agent_core-0.9.2/tests/test_gatekeeper.py +309 -0
- ffwf_tau_agent_core-0.9.2/tests/test_hook_dispatcher_threading.py +164 -0
- ffwf_tau_agent_core-0.9.2/tests/test_input_hook.py +347 -0
- ffwf_tau_agent_core-0.9.2/tests/test_inter_extension_channels.py +352 -0
- ffwf_tau_agent_core-0.9.2/tests/test_load_extensions_wiring.py +253 -0
- ffwf_tau_agent_core-0.9.2/tests/test_model_usage_access.py +240 -0
- ffwf_tau_agent_core-0.9.2/tests/test_nats_bus_extension.py +1331 -0
- ffwf_tau_agent_core-0.9.2/tests/test_no_tools_policy.py +217 -0
- ffwf_tau_agent_core-0.9.2/tests/test_prefix_stability_fanout.py +212 -0
- ffwf_tau_agent_core-0.9.2/tests/test_reminders.py +583 -0
- ffwf_tau_agent_core-0.9.2/tests/test_rpc.py +1868 -0
- ffwf_tau_agent_core-0.9.2/tests/test_rpc_backpressure.py +550 -0
- ffwf_tau_agent_core-0.9.2/tests/test_rpc_capabilities.py +207 -0
- ffwf_tau_agent_core-0.9.2/tests/test_rpc_capability_audit.py +639 -0
- ffwf_tau_agent_core-0.9.2/tests/test_rpc_event_schema.py +429 -0
- ffwf_tau_agent_core-0.9.2/tests/test_rpc_protocol_doc.py +188 -0
- ffwf_tau_agent_core-0.9.2/tests/test_rpc_tier_b_compact.py +1459 -0
- ffwf_tau_agent_core-0.9.2/tests/test_rpc_tier_b_get_last_assistant_text.py +437 -0
- ffwf_tau_agent_core-0.9.2/tests/test_rpc_tier_b_get_models.py +348 -0
- ffwf_tau_agent_core-0.9.2/tests/test_rpc_tier_b_get_session_stats.py +451 -0
- ffwf_tau_agent_core-0.9.2/tests/test_rpc_tier_b_list_sessions.py +731 -0
- ffwf_tau_agent_core-0.9.2/tests/test_rpc_tier_b_scaffolding.py +638 -0
- ffwf_tau_agent_core-0.9.2/tests/test_rpc_tier_b_set_auto_compaction.py +470 -0
- ffwf_tau_agent_core-0.9.2/tests/test_rpc_tier_b_set_model.py +380 -0
- ffwf_tau_agent_core-0.9.2/tests/test_rpc_tier_b_set_session_name.py +283 -0
- ffwf_tau_agent_core-0.9.2/tests/test_rpc_transport.py +1385 -0
- ffwf_tau_agent_core-0.9.2/tests/test_runtime_extension_mgmt.py +273 -0
- ffwf_tau_agent_core-0.9.2/tests/test_send_message_durable.py +130 -0
- ffwf_tau_agent_core-0.9.2/tests/test_session.py +336 -0
- ffwf_tau_agent_core-0.9.2/tests/test_session_catalog.py +323 -0
- ffwf_tau_agent_core-0.9.2/tests/test_session_lifecycle_hooks.py +140 -0
- ffwf_tau_agent_core-0.9.2/tests/test_session_log.py +180 -0
- ffwf_tau_agent_core-0.9.2/tests/test_session_manager.py +1262 -0
- ffwf_tau_agent_core-0.9.2/tests/test_session_operations.py +1129 -0
- ffwf_tau_agent_core-0.9.2/tests/test_session_types.py +305 -0
- ffwf_tau_agent_core-0.9.2/tests/test_settings.py +223 -0
- ffwf_tau_agent_core-0.9.2/tests/test_side_usage.py +282 -0
- ffwf_tau_agent_core-0.9.2/tests/test_spawn_branch.py +262 -0
- ffwf_tau_agent_core-0.9.2/tests/test_submission.py +249 -0
- ffwf_tau_agent_core-0.9.2/tests/test_submission_capabilities.py +436 -0
- ffwf_tau_agent_core-0.9.2/tests/test_submission_span_channel.py +198 -0
- ffwf_tau_agent_core-0.9.2/tests/test_submit_admission.py +351 -0
- ffwf_tau_agent_core-0.9.2/tests/test_submit_commands.py +402 -0
- ffwf_tau_agent_core-0.9.2/tests/test_submit_depth.py +325 -0
- ffwf_tau_agent_core-0.9.2/tests/test_submit_fork_rollback.py +468 -0
- ffwf_tau_agent_core-0.9.2/tests/test_submit_provenance.py +133 -0
- ffwf_tau_agent_core-0.9.2/tests/test_submit_reentrant.py +134 -0
- ffwf_tau_agent_core-0.9.2/tests/test_submit_steer.py +528 -0
- ffwf_tau_agent_core-0.9.2/tests/test_submit_threadsafe.py +369 -0
- ffwf_tau_agent_core-0.9.2/tests/test_tempo_table.py +639 -0
- ffwf_tau_agent_core-0.9.2/tests/test_tool_call_hook.py +252 -0
- ffwf_tau_agent_core-0.9.2/tests/test_tool_result_hook.py +240 -0
- ffwf_tau_agent_core-0.9.2/tests/test_tools_base.py +528 -0
- ffwf_tau_agent_core-0.9.2/tests/test_tools_built_in.py +1271 -0
- ffwf_tau_agent_core-0.9.2/tests/test_trigger_compact.py +224 -0
- ffwf_tau_agent_core-0.9.2/tests/test_turn_end_hook.py +410 -0
- ffwf_tau_agent_core-0.9.2/tests/test_ui_delegate.py +289 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Fight Fire with Fire Robotics, LLC
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ffwf-tau-agent-core
|
|
3
|
+
Version: 0.9.2
|
|
4
|
+
Summary: τ-agent-core: Agent runtime, loop, tools, sessions, extensions
|
|
5
|
+
Author: Fight Fire with Fire Robotics, LLC
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://ffwfrobotics.github.io/tau/
|
|
8
|
+
Project-URL: Documentation, https://ffwfrobotics.github.io/tau/
|
|
9
|
+
Project-URL: Repository, https://github.com/jmccardle/tau
|
|
10
|
+
Project-URL: Issues, https://github.com/jmccardle/tau/issues
|
|
11
|
+
Requires-Python: >=3.11
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: ffwf-tau-llm==0.9.2
|
|
15
|
+
Requires-Dist: pydantic>=2.0
|
|
16
|
+
Provides-Extra: bus
|
|
17
|
+
Requires-Dist: nats-py>=2.15.0; extra == "bus"
|
|
18
|
+
Provides-Extra: testing
|
|
19
|
+
Requires-Dist: pytest; extra == "testing"
|
|
20
|
+
Provides-Extra: dev
|
|
21
|
+
Requires-Dist: ffwf-tau-agent-core[bus,testing]==0.9.2; extra == "dev"
|
|
22
|
+
Requires-Dist: pytest-asyncio; extra == "dev"
|
|
23
|
+
Requires-Dist: ruff; extra == "dev"
|
|
24
|
+
Requires-Dist: mypy==2.1.*; extra == "dev"
|
|
25
|
+
Dynamic: license-file
|
|
26
|
+
|
|
27
|
+
# ffwf-tau-agent-core
|
|
28
|
+
|
|
29
|
+
The runtime of **Tau**, a programmable coding agent harness. `tau_agent_core` is
|
|
30
|
+
the loop that drives a conversation: it calls the model, executes tools, appends
|
|
31
|
+
entries to a session, dispatches extension hooks, and compacts context when it
|
|
32
|
+
grows too large.
|
|
33
|
+
|
|
34
|
+
It is headless. No Textual, no stdout assumptions, and `tau_agent_core` never
|
|
35
|
+
imports `tau_coding_agent`. Embed it in your own program, drive it as a
|
|
36
|
+
subprocess over RPC, or run it under Tau's TUI.
|
|
37
|
+
|
|
38
|
+
Tau began as a Python port of the TypeScript project
|
|
39
|
+
[pi-mono](https://github.com/badlogic/pi-mono), which is still read as the
|
|
40
|
+
reference implementation when porting or debugging; it now diverges from pi
|
|
41
|
+
deliberately in several places.
|
|
42
|
+
|
|
43
|
+
## What is in it
|
|
44
|
+
|
|
45
|
+
- **`AgentSession`** — the object you hold. **The one door:** every input source
|
|
46
|
+
— TUI keystrokes, `tau -p`, the SDK, an extension, an RPC client — funnels
|
|
47
|
+
through `AgentSession.submit()`. `prompt()` is a thin wrapper that builds a
|
|
48
|
+
`Submission` and calls `submit()`, not a second door. Concurrent submissions
|
|
49
|
+
have a stated policy (`multitask_strategy`: reject, enqueue, steer, rollback,
|
|
50
|
+
fork) rather than an answer improvised per caller.
|
|
51
|
+
- **`create_agent_session()`** — the SDK factory. Resolves a model name, built-in
|
|
52
|
+
tool names, and extension callables into a working session.
|
|
53
|
+
- **Built-in tools** — `read`, `write`, `edit`, `bash`, `ls`, `grep`, `find`.
|
|
54
|
+
- **Sessions are a tree, not a chat log.** Entries are append-only;
|
|
55
|
+
`ConversationTree` walks `parent_id` chains to build model input for the
|
|
56
|
+
active leaf. Fork, branch, rollback, and running a second agent from an
|
|
57
|
+
earlier point in the conversation all fall out of that structure instead of
|
|
58
|
+
being bolted on.
|
|
59
|
+
- **Storage is a seam.** `SessionLog` and `SessionCatalog` are protocols. An
|
|
60
|
+
in-memory log ships here, a file store ships with `ffwf-tau-coding-agent`, and
|
|
61
|
+
a JMFTS-backed store ships with `ffwf-tau-jmfts`.
|
|
62
|
+
- **Extensions are plain Python modules** — `importlib`, no compile step, no
|
|
63
|
+
manifest language. They register tools and commands, subscribe to lifecycle
|
|
64
|
+
events, mutate what the loop is about to do, carry per-extension config, and
|
|
65
|
+
can veto a tool call.
|
|
66
|
+
- **Compaction is LLM-backed with no fabricated-summary fallback.** A compaction
|
|
67
|
+
error raises rather than silently truncating the conversation.
|
|
68
|
+
- **RPC** — a versioned JSON-RPC 2.0 command surface, so τ can be driven as a
|
|
69
|
+
process rather than imported.
|
|
70
|
+
- **Export** — a session to Markdown or HTML.
|
|
71
|
+
|
|
72
|
+
## Why it is a separate package
|
|
73
|
+
|
|
74
|
+
The runtime and the terminal interface are different concerns with different
|
|
75
|
+
dependency footprints. Keeping them apart is what lets a server, a bot, or a
|
|
76
|
+
test harness run the same agent without a UI toolkit in sight.
|
|
77
|
+
|
|
78
|
+
## Install
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
pip install ffwf-tau-agent-core
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Python 3.11 or newer. Pulls in `ffwf-tau-llm`.
|
|
85
|
+
|
|
86
|
+
Two extras, both off by default:
|
|
87
|
+
|
|
88
|
+
| Extra | Adds | Needed for |
|
|
89
|
+
|---|---|---|
|
|
90
|
+
| `ffwf-tau-agent-core[bus]` | `nats-py` | the built-in `nats_bus` extension, which publishes to NATS subjects |
|
|
91
|
+
| `ffwf-tau-agent-core[testing]` | `pytest` | importing `tau_agent_core.testing`, the store contract suites |
|
|
92
|
+
|
|
93
|
+
A plain install stays pytest-free and NATS-free.
|
|
94
|
+
|
|
95
|
+
## Example
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
import asyncio
|
|
99
|
+
from tau_agent_core import create_agent_session
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
async def main():
|
|
103
|
+
session = create_agent_session(
|
|
104
|
+
model="gpt-4o",
|
|
105
|
+
tools=["read", "grep", "bash"],
|
|
106
|
+
)
|
|
107
|
+
session.subscribe(lambda event: print(event.type))
|
|
108
|
+
|
|
109
|
+
messages = await session.prompt("What files are in this directory?")
|
|
110
|
+
for message in messages:
|
|
111
|
+
print(message.get("role"), message.get("content"))
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
asyncio.run(main())
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
`prompt()` returns the messages produced by **this turn**, not the whole
|
|
118
|
+
conversation. The full history lives in the session's `SessionLog`.
|
|
119
|
+
|
|
120
|
+
`tools=` takes built-in name strings only, and raises on a name it does not
|
|
121
|
+
recognise. A custom `AgentTool` goes through the `AgentSession` constructor
|
|
122
|
+
directly, or is registered by an extension.
|
|
123
|
+
|
|
124
|
+
## Writing an extension
|
|
125
|
+
|
|
126
|
+
An extension is a module with a `register` callable that receives an
|
|
127
|
+
`ExtensionAPI`. This one refuses a destructive shell command before it runs:
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
def permission_gate_tool_call(event, ctx):
|
|
131
|
+
command = (event.get("input") or {}).get("command", "")
|
|
132
|
+
if event["tool_name"] == "bash" and "rm -rf /" in command:
|
|
133
|
+
return {"block": True, "reason": "destructive command refused"}
|
|
134
|
+
return None
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def register(api):
|
|
138
|
+
api.on("tool_call", permission_gate_tool_call)
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
**`tool_call` is a mutating hook, not a notification.** Its return value is
|
|
142
|
+
honoured, and a `block` becomes an error tool result the model can react to.
|
|
143
|
+
Notify-only events such as `tool_execution_start` have their return value
|
|
144
|
+
discarded and cannot stop anything — a gate written against one prints a warning
|
|
145
|
+
and then lets the command run.
|
|
146
|
+
|
|
147
|
+
A module written this way loads from a path: `tau -e permission_gate.py`. The
|
|
148
|
+
repository's `examples/` directory holds around thirty working extensions.
|
|
149
|
+
|
|
150
|
+
## Testing your own store
|
|
151
|
+
|
|
152
|
+
`tau_agent_core.testing` ships the conformance suites for the two storage seams,
|
|
153
|
+
so a store written elsewhere can be held to the same contract. The contract is
|
|
154
|
+
the code, not a document.
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
from tau_agent_core.testing import SessionCatalogContractTests, SessionLogContractTests
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class TestMyLog(SessionLogContractTests):
|
|
161
|
+
def make_log(self):
|
|
162
|
+
return MyStore(...)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class TestMyCatalog(SessionCatalogContractTests):
|
|
166
|
+
def make_catalog(self):
|
|
167
|
+
return MyCatalog(...)
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Install `ffwf-tau-agent-core[testing]` to import that module.
|
|
171
|
+
|
|
172
|
+
## Docs
|
|
173
|
+
|
|
174
|
+
- `docs/tau-agent-core.md` — design notes for this package.
|
|
175
|
+
- `docs/SUBMISSION-LIFECYCLE.md` — `submit()` and the concurrency strategies.
|
|
176
|
+
- `docs/NODE-ADDRESSABLE-AGENTS.md` — the session-tree invariants.
|
|
177
|
+
- `docs/extensions.md`, `docs/EXTENSIONS-WALKTHROUGH.md` — the extension API.
|
|
178
|
+
- `docs/REMOTE-CONTROL.md`, `docs/RPC-PROTOCOL.md` — driving τ as a subprocess.
|
|
179
|
+
|
|
180
|
+
Repository: <https://github.com/jmccardle/tau>
|
|
181
|
+
|
|
182
|
+
## The rest of Tau
|
|
183
|
+
|
|
184
|
+
| Distribution | Imports as | What it is |
|
|
185
|
+
|---|---|---|
|
|
186
|
+
| `ffwf-tau-llm` | `tau_llm` | the provider and streaming layer this sits on |
|
|
187
|
+
| `ffwf-tau-coding-agent` | `tau_coding_agent` | the `tau` command and the Textual TUI |
|
|
188
|
+
| `ffwf-tau-jmfts` | `tau_jmfts` | a JMFTS-backed session store |
|
|
189
|
+
|
|
190
|
+
MIT © Fight Fire with Fire Robotics, LLC
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# ffwf-tau-agent-core
|
|
2
|
+
|
|
3
|
+
The runtime of **Tau**, a programmable coding agent harness. `tau_agent_core` is
|
|
4
|
+
the loop that drives a conversation: it calls the model, executes tools, appends
|
|
5
|
+
entries to a session, dispatches extension hooks, and compacts context when it
|
|
6
|
+
grows too large.
|
|
7
|
+
|
|
8
|
+
It is headless. No Textual, no stdout assumptions, and `tau_agent_core` never
|
|
9
|
+
imports `tau_coding_agent`. Embed it in your own program, drive it as a
|
|
10
|
+
subprocess over RPC, or run it under Tau's TUI.
|
|
11
|
+
|
|
12
|
+
Tau began as a Python port of the TypeScript project
|
|
13
|
+
[pi-mono](https://github.com/badlogic/pi-mono), which is still read as the
|
|
14
|
+
reference implementation when porting or debugging; it now diverges from pi
|
|
15
|
+
deliberately in several places.
|
|
16
|
+
|
|
17
|
+
## What is in it
|
|
18
|
+
|
|
19
|
+
- **`AgentSession`** — the object you hold. **The one door:** every input source
|
|
20
|
+
— TUI keystrokes, `tau -p`, the SDK, an extension, an RPC client — funnels
|
|
21
|
+
through `AgentSession.submit()`. `prompt()` is a thin wrapper that builds a
|
|
22
|
+
`Submission` and calls `submit()`, not a second door. Concurrent submissions
|
|
23
|
+
have a stated policy (`multitask_strategy`: reject, enqueue, steer, rollback,
|
|
24
|
+
fork) rather than an answer improvised per caller.
|
|
25
|
+
- **`create_agent_session()`** — the SDK factory. Resolves a model name, built-in
|
|
26
|
+
tool names, and extension callables into a working session.
|
|
27
|
+
- **Built-in tools** — `read`, `write`, `edit`, `bash`, `ls`, `grep`, `find`.
|
|
28
|
+
- **Sessions are a tree, not a chat log.** Entries are append-only;
|
|
29
|
+
`ConversationTree` walks `parent_id` chains to build model input for the
|
|
30
|
+
active leaf. Fork, branch, rollback, and running a second agent from an
|
|
31
|
+
earlier point in the conversation all fall out of that structure instead of
|
|
32
|
+
being bolted on.
|
|
33
|
+
- **Storage is a seam.** `SessionLog` and `SessionCatalog` are protocols. An
|
|
34
|
+
in-memory log ships here, a file store ships with `ffwf-tau-coding-agent`, and
|
|
35
|
+
a JMFTS-backed store ships with `ffwf-tau-jmfts`.
|
|
36
|
+
- **Extensions are plain Python modules** — `importlib`, no compile step, no
|
|
37
|
+
manifest language. They register tools and commands, subscribe to lifecycle
|
|
38
|
+
events, mutate what the loop is about to do, carry per-extension config, and
|
|
39
|
+
can veto a tool call.
|
|
40
|
+
- **Compaction is LLM-backed with no fabricated-summary fallback.** A compaction
|
|
41
|
+
error raises rather than silently truncating the conversation.
|
|
42
|
+
- **RPC** — a versioned JSON-RPC 2.0 command surface, so τ can be driven as a
|
|
43
|
+
process rather than imported.
|
|
44
|
+
- **Export** — a session to Markdown or HTML.
|
|
45
|
+
|
|
46
|
+
## Why it is a separate package
|
|
47
|
+
|
|
48
|
+
The runtime and the terminal interface are different concerns with different
|
|
49
|
+
dependency footprints. Keeping them apart is what lets a server, a bot, or a
|
|
50
|
+
test harness run the same agent without a UI toolkit in sight.
|
|
51
|
+
|
|
52
|
+
## Install
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pip install ffwf-tau-agent-core
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Python 3.11 or newer. Pulls in `ffwf-tau-llm`.
|
|
59
|
+
|
|
60
|
+
Two extras, both off by default:
|
|
61
|
+
|
|
62
|
+
| Extra | Adds | Needed for |
|
|
63
|
+
|---|---|---|
|
|
64
|
+
| `ffwf-tau-agent-core[bus]` | `nats-py` | the built-in `nats_bus` extension, which publishes to NATS subjects |
|
|
65
|
+
| `ffwf-tau-agent-core[testing]` | `pytest` | importing `tau_agent_core.testing`, the store contract suites |
|
|
66
|
+
|
|
67
|
+
A plain install stays pytest-free and NATS-free.
|
|
68
|
+
|
|
69
|
+
## Example
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
import asyncio
|
|
73
|
+
from tau_agent_core import create_agent_session
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
async def main():
|
|
77
|
+
session = create_agent_session(
|
|
78
|
+
model="gpt-4o",
|
|
79
|
+
tools=["read", "grep", "bash"],
|
|
80
|
+
)
|
|
81
|
+
session.subscribe(lambda event: print(event.type))
|
|
82
|
+
|
|
83
|
+
messages = await session.prompt("What files are in this directory?")
|
|
84
|
+
for message in messages:
|
|
85
|
+
print(message.get("role"), message.get("content"))
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
asyncio.run(main())
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
`prompt()` returns the messages produced by **this turn**, not the whole
|
|
92
|
+
conversation. The full history lives in the session's `SessionLog`.
|
|
93
|
+
|
|
94
|
+
`tools=` takes built-in name strings only, and raises on a name it does not
|
|
95
|
+
recognise. A custom `AgentTool` goes through the `AgentSession` constructor
|
|
96
|
+
directly, or is registered by an extension.
|
|
97
|
+
|
|
98
|
+
## Writing an extension
|
|
99
|
+
|
|
100
|
+
An extension is a module with a `register` callable that receives an
|
|
101
|
+
`ExtensionAPI`. This one refuses a destructive shell command before it runs:
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
def permission_gate_tool_call(event, ctx):
|
|
105
|
+
command = (event.get("input") or {}).get("command", "")
|
|
106
|
+
if event["tool_name"] == "bash" and "rm -rf /" in command:
|
|
107
|
+
return {"block": True, "reason": "destructive command refused"}
|
|
108
|
+
return None
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def register(api):
|
|
112
|
+
api.on("tool_call", permission_gate_tool_call)
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**`tool_call` is a mutating hook, not a notification.** Its return value is
|
|
116
|
+
honoured, and a `block` becomes an error tool result the model can react to.
|
|
117
|
+
Notify-only events such as `tool_execution_start` have their return value
|
|
118
|
+
discarded and cannot stop anything — a gate written against one prints a warning
|
|
119
|
+
and then lets the command run.
|
|
120
|
+
|
|
121
|
+
A module written this way loads from a path: `tau -e permission_gate.py`. The
|
|
122
|
+
repository's `examples/` directory holds around thirty working extensions.
|
|
123
|
+
|
|
124
|
+
## Testing your own store
|
|
125
|
+
|
|
126
|
+
`tau_agent_core.testing` ships the conformance suites for the two storage seams,
|
|
127
|
+
so a store written elsewhere can be held to the same contract. The contract is
|
|
128
|
+
the code, not a document.
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
from tau_agent_core.testing import SessionCatalogContractTests, SessionLogContractTests
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class TestMyLog(SessionLogContractTests):
|
|
135
|
+
def make_log(self):
|
|
136
|
+
return MyStore(...)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class TestMyCatalog(SessionCatalogContractTests):
|
|
140
|
+
def make_catalog(self):
|
|
141
|
+
return MyCatalog(...)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Install `ffwf-tau-agent-core[testing]` to import that module.
|
|
145
|
+
|
|
146
|
+
## Docs
|
|
147
|
+
|
|
148
|
+
- `docs/tau-agent-core.md` — design notes for this package.
|
|
149
|
+
- `docs/SUBMISSION-LIFECYCLE.md` — `submit()` and the concurrency strategies.
|
|
150
|
+
- `docs/NODE-ADDRESSABLE-AGENTS.md` — the session-tree invariants.
|
|
151
|
+
- `docs/extensions.md`, `docs/EXTENSIONS-WALKTHROUGH.md` — the extension API.
|
|
152
|
+
- `docs/REMOTE-CONTROL.md`, `docs/RPC-PROTOCOL.md` — driving τ as a subprocess.
|
|
153
|
+
|
|
154
|
+
Repository: <https://github.com/jmccardle/tau>
|
|
155
|
+
|
|
156
|
+
## The rest of Tau
|
|
157
|
+
|
|
158
|
+
| Distribution | Imports as | What it is |
|
|
159
|
+
|---|---|---|
|
|
160
|
+
| `ffwf-tau-llm` | `tau_llm` | the provider and streaming layer this sits on |
|
|
161
|
+
| `ffwf-tau-coding-agent` | `tau_coding_agent` | the `tau` command and the Textual TUI |
|
|
162
|
+
| `ffwf-tau-jmfts` | `tau_jmfts` | a JMFTS-backed session store |
|
|
163
|
+
|
|
164
|
+
MIT © Fight Fire with Fire Robotics, LLC
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "ffwf-tau-agent-core"
|
|
3
|
+
# The version is not a literal here. Each distribution reads it from its own
|
|
4
|
+
# package's ``__version__``, so the wheel metadata and what the code reports can
|
|
5
|
+
# never disagree -- they are the same line. Bump that line to cut a release.
|
|
6
|
+
dynamic = ["version"]
|
|
7
|
+
description = "τ-agent-core: Agent runtime, loop, tools, sessions, extensions"
|
|
8
|
+
requires-python = ">=3.11"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
# SPDX expression + the file itself (PEP 639), which is why build-system below
|
|
11
|
+
# needs setuptools >= 77.0.3: earlier versions ignore `license-files` and the
|
|
12
|
+
# wheel ships without the licence text. Each package carries its own copy of the
|
|
13
|
+
# repo-root LICENSE because a wheel is built from one package directory and
|
|
14
|
+
# cannot reach outside it.
|
|
15
|
+
license = "MIT"
|
|
16
|
+
license-files = ["LICENSE"]
|
|
17
|
+
authors = [{ name = "Fight Fire with Fire Robotics, LLC" }]
|
|
18
|
+
dependencies = [
|
|
19
|
+
"ffwf-tau-llm==0.9.2",
|
|
20
|
+
"pydantic>=2.0",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[project.optional-dependencies]
|
|
24
|
+
# The message bus. nats-py is the ONLY importer of anything outside the stdlib in
|
|
25
|
+
# extensions_builtin/nats_bus.py, and that file is not imported by this package --
|
|
26
|
+
# it is loaded by path, through the ordinary file-extension route, and only when
|
|
27
|
+
# the operator passes --bus. A capability gated behind a CLI flag should not be a
|
|
28
|
+
# hard dependency of every headless install, so it lives here.
|
|
29
|
+
#
|
|
30
|
+
# This is also the Tectum seam: Tectum is not a Python package and never appears
|
|
31
|
+
# in any dependency list. τ reaches it by publishing to NATS subjects from
|
|
32
|
+
# nats_bus.py, so `[bus]` is what an integrator installs.
|
|
33
|
+
bus = ["nats-py>=2.15.0"]
|
|
34
|
+
# tau_agent_core.testing ships the SessionCatalog / SessionLog contract suites
|
|
35
|
+
# for other projects to run against their own stores, and those modules import
|
|
36
|
+
# pytest at module scope. Consumers install `ffwf-tau-agent-core[testing]`; a plain
|
|
37
|
+
# install stays pytest-free.
|
|
38
|
+
testing = ["pytest"]
|
|
39
|
+
dev = [
|
|
40
|
+
"ffwf-tau-agent-core[bus,testing]==0.9.2",
|
|
41
|
+
"pytest-asyncio",
|
|
42
|
+
"ruff",
|
|
43
|
+
# Pinned, unlike every other dev tool here. mypy has no "state your rule set"
|
|
44
|
+
# knob the way ruff does, so its findings move with its version: verified on
|
|
45
|
+
# this tree, 2.1.0 reports 0 errors and 2.3.1 reports 50 across 12 files.
|
|
46
|
+
# Those 50 look like real findings that a newer mypy got better at seeing --
|
|
47
|
+
# mostly no-any-return -- so they are scheduled, not suppressed: clearing
|
|
48
|
+
# them is its own pass, and until then the gate must mean the same thing in
|
|
49
|
+
# every clone. Raise this bound WITH that pass, never to make a hook go green.
|
|
50
|
+
"mypy==2.1.*",
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
# The PUBLIC repository only. This tree has a second, private git remote whose
|
|
54
|
+
# history is unfiltered; its hostname must never reach this file or a README,
|
|
55
|
+
# because both are uploaded verbatim (pyproject.toml ships inside the sdist).
|
|
56
|
+
# tests/test_packaging.py enforces that.
|
|
57
|
+
[project.urls]
|
|
58
|
+
Homepage = "https://ffwfrobotics.github.io/tau/"
|
|
59
|
+
Documentation = "https://ffwfrobotics.github.io/tau/"
|
|
60
|
+
Repository = "https://github.com/jmccardle/tau"
|
|
61
|
+
Issues = "https://github.com/jmccardle/tau/issues"
|
|
62
|
+
|
|
63
|
+
[build-system]
|
|
64
|
+
requires = ["setuptools>=77.0.3", "wheel"]
|
|
65
|
+
build-backend = "setuptools.build_meta"
|
|
66
|
+
|
|
67
|
+
[tool.setuptools.packages.find]
|
|
68
|
+
where = ["src"]
|
|
69
|
+
|
|
70
|
+
[tool.setuptools.dynamic]
|
|
71
|
+
version = { attr = "tau_agent_core.__version__" }
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ffwf-tau-agent-core
|
|
3
|
+
Version: 0.9.2
|
|
4
|
+
Summary: τ-agent-core: Agent runtime, loop, tools, sessions, extensions
|
|
5
|
+
Author: Fight Fire with Fire Robotics, LLC
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://ffwfrobotics.github.io/tau/
|
|
8
|
+
Project-URL: Documentation, https://ffwfrobotics.github.io/tau/
|
|
9
|
+
Project-URL: Repository, https://github.com/jmccardle/tau
|
|
10
|
+
Project-URL: Issues, https://github.com/jmccardle/tau/issues
|
|
11
|
+
Requires-Python: >=3.11
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: ffwf-tau-llm==0.9.2
|
|
15
|
+
Requires-Dist: pydantic>=2.0
|
|
16
|
+
Provides-Extra: bus
|
|
17
|
+
Requires-Dist: nats-py>=2.15.0; extra == "bus"
|
|
18
|
+
Provides-Extra: testing
|
|
19
|
+
Requires-Dist: pytest; extra == "testing"
|
|
20
|
+
Provides-Extra: dev
|
|
21
|
+
Requires-Dist: ffwf-tau-agent-core[bus,testing]==0.9.2; extra == "dev"
|
|
22
|
+
Requires-Dist: pytest-asyncio; extra == "dev"
|
|
23
|
+
Requires-Dist: ruff; extra == "dev"
|
|
24
|
+
Requires-Dist: mypy==2.1.*; extra == "dev"
|
|
25
|
+
Dynamic: license-file
|
|
26
|
+
|
|
27
|
+
# ffwf-tau-agent-core
|
|
28
|
+
|
|
29
|
+
The runtime of **Tau**, a programmable coding agent harness. `tau_agent_core` is
|
|
30
|
+
the loop that drives a conversation: it calls the model, executes tools, appends
|
|
31
|
+
entries to a session, dispatches extension hooks, and compacts context when it
|
|
32
|
+
grows too large.
|
|
33
|
+
|
|
34
|
+
It is headless. No Textual, no stdout assumptions, and `tau_agent_core` never
|
|
35
|
+
imports `tau_coding_agent`. Embed it in your own program, drive it as a
|
|
36
|
+
subprocess over RPC, or run it under Tau's TUI.
|
|
37
|
+
|
|
38
|
+
Tau began as a Python port of the TypeScript project
|
|
39
|
+
[pi-mono](https://github.com/badlogic/pi-mono), which is still read as the
|
|
40
|
+
reference implementation when porting or debugging; it now diverges from pi
|
|
41
|
+
deliberately in several places.
|
|
42
|
+
|
|
43
|
+
## What is in it
|
|
44
|
+
|
|
45
|
+
- **`AgentSession`** — the object you hold. **The one door:** every input source
|
|
46
|
+
— TUI keystrokes, `tau -p`, the SDK, an extension, an RPC client — funnels
|
|
47
|
+
through `AgentSession.submit()`. `prompt()` is a thin wrapper that builds a
|
|
48
|
+
`Submission` and calls `submit()`, not a second door. Concurrent submissions
|
|
49
|
+
have a stated policy (`multitask_strategy`: reject, enqueue, steer, rollback,
|
|
50
|
+
fork) rather than an answer improvised per caller.
|
|
51
|
+
- **`create_agent_session()`** — the SDK factory. Resolves a model name, built-in
|
|
52
|
+
tool names, and extension callables into a working session.
|
|
53
|
+
- **Built-in tools** — `read`, `write`, `edit`, `bash`, `ls`, `grep`, `find`.
|
|
54
|
+
- **Sessions are a tree, not a chat log.** Entries are append-only;
|
|
55
|
+
`ConversationTree` walks `parent_id` chains to build model input for the
|
|
56
|
+
active leaf. Fork, branch, rollback, and running a second agent from an
|
|
57
|
+
earlier point in the conversation all fall out of that structure instead of
|
|
58
|
+
being bolted on.
|
|
59
|
+
- **Storage is a seam.** `SessionLog` and `SessionCatalog` are protocols. An
|
|
60
|
+
in-memory log ships here, a file store ships with `ffwf-tau-coding-agent`, and
|
|
61
|
+
a JMFTS-backed store ships with `ffwf-tau-jmfts`.
|
|
62
|
+
- **Extensions are plain Python modules** — `importlib`, no compile step, no
|
|
63
|
+
manifest language. They register tools and commands, subscribe to lifecycle
|
|
64
|
+
events, mutate what the loop is about to do, carry per-extension config, and
|
|
65
|
+
can veto a tool call.
|
|
66
|
+
- **Compaction is LLM-backed with no fabricated-summary fallback.** A compaction
|
|
67
|
+
error raises rather than silently truncating the conversation.
|
|
68
|
+
- **RPC** — a versioned JSON-RPC 2.0 command surface, so τ can be driven as a
|
|
69
|
+
process rather than imported.
|
|
70
|
+
- **Export** — a session to Markdown or HTML.
|
|
71
|
+
|
|
72
|
+
## Why it is a separate package
|
|
73
|
+
|
|
74
|
+
The runtime and the terminal interface are different concerns with different
|
|
75
|
+
dependency footprints. Keeping them apart is what lets a server, a bot, or a
|
|
76
|
+
test harness run the same agent without a UI toolkit in sight.
|
|
77
|
+
|
|
78
|
+
## Install
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
pip install ffwf-tau-agent-core
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Python 3.11 or newer. Pulls in `ffwf-tau-llm`.
|
|
85
|
+
|
|
86
|
+
Two extras, both off by default:
|
|
87
|
+
|
|
88
|
+
| Extra | Adds | Needed for |
|
|
89
|
+
|---|---|---|
|
|
90
|
+
| `ffwf-tau-agent-core[bus]` | `nats-py` | the built-in `nats_bus` extension, which publishes to NATS subjects |
|
|
91
|
+
| `ffwf-tau-agent-core[testing]` | `pytest` | importing `tau_agent_core.testing`, the store contract suites |
|
|
92
|
+
|
|
93
|
+
A plain install stays pytest-free and NATS-free.
|
|
94
|
+
|
|
95
|
+
## Example
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
import asyncio
|
|
99
|
+
from tau_agent_core import create_agent_session
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
async def main():
|
|
103
|
+
session = create_agent_session(
|
|
104
|
+
model="gpt-4o",
|
|
105
|
+
tools=["read", "grep", "bash"],
|
|
106
|
+
)
|
|
107
|
+
session.subscribe(lambda event: print(event.type))
|
|
108
|
+
|
|
109
|
+
messages = await session.prompt("What files are in this directory?")
|
|
110
|
+
for message in messages:
|
|
111
|
+
print(message.get("role"), message.get("content"))
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
asyncio.run(main())
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
`prompt()` returns the messages produced by **this turn**, not the whole
|
|
118
|
+
conversation. The full history lives in the session's `SessionLog`.
|
|
119
|
+
|
|
120
|
+
`tools=` takes built-in name strings only, and raises on a name it does not
|
|
121
|
+
recognise. A custom `AgentTool` goes through the `AgentSession` constructor
|
|
122
|
+
directly, or is registered by an extension.
|
|
123
|
+
|
|
124
|
+
## Writing an extension
|
|
125
|
+
|
|
126
|
+
An extension is a module with a `register` callable that receives an
|
|
127
|
+
`ExtensionAPI`. This one refuses a destructive shell command before it runs:
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
def permission_gate_tool_call(event, ctx):
|
|
131
|
+
command = (event.get("input") or {}).get("command", "")
|
|
132
|
+
if event["tool_name"] == "bash" and "rm -rf /" in command:
|
|
133
|
+
return {"block": True, "reason": "destructive command refused"}
|
|
134
|
+
return None
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def register(api):
|
|
138
|
+
api.on("tool_call", permission_gate_tool_call)
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
**`tool_call` is a mutating hook, not a notification.** Its return value is
|
|
142
|
+
honoured, and a `block` becomes an error tool result the model can react to.
|
|
143
|
+
Notify-only events such as `tool_execution_start` have their return value
|
|
144
|
+
discarded and cannot stop anything — a gate written against one prints a warning
|
|
145
|
+
and then lets the command run.
|
|
146
|
+
|
|
147
|
+
A module written this way loads from a path: `tau -e permission_gate.py`. The
|
|
148
|
+
repository's `examples/` directory holds around thirty working extensions.
|
|
149
|
+
|
|
150
|
+
## Testing your own store
|
|
151
|
+
|
|
152
|
+
`tau_agent_core.testing` ships the conformance suites for the two storage seams,
|
|
153
|
+
so a store written elsewhere can be held to the same contract. The contract is
|
|
154
|
+
the code, not a document.
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
from tau_agent_core.testing import SessionCatalogContractTests, SessionLogContractTests
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class TestMyLog(SessionLogContractTests):
|
|
161
|
+
def make_log(self):
|
|
162
|
+
return MyStore(...)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class TestMyCatalog(SessionCatalogContractTests):
|
|
166
|
+
def make_catalog(self):
|
|
167
|
+
return MyCatalog(...)
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Install `ffwf-tau-agent-core[testing]` to import that module.
|
|
171
|
+
|
|
172
|
+
## Docs
|
|
173
|
+
|
|
174
|
+
- `docs/tau-agent-core.md` — design notes for this package.
|
|
175
|
+
- `docs/SUBMISSION-LIFECYCLE.md` — `submit()` and the concurrency strategies.
|
|
176
|
+
- `docs/NODE-ADDRESSABLE-AGENTS.md` — the session-tree invariants.
|
|
177
|
+
- `docs/extensions.md`, `docs/EXTENSIONS-WALKTHROUGH.md` — the extension API.
|
|
178
|
+
- `docs/REMOTE-CONTROL.md`, `docs/RPC-PROTOCOL.md` — driving τ as a subprocess.
|
|
179
|
+
|
|
180
|
+
Repository: <https://github.com/jmccardle/tau>
|
|
181
|
+
|
|
182
|
+
## The rest of Tau
|
|
183
|
+
|
|
184
|
+
| Distribution | Imports as | What it is |
|
|
185
|
+
|---|---|---|
|
|
186
|
+
| `ffwf-tau-llm` | `tau_llm` | the provider and streaming layer this sits on |
|
|
187
|
+
| `ffwf-tau-coding-agent` | `tau_coding_agent` | the `tau` command and the Textual TUI |
|
|
188
|
+
| `ffwf-tau-jmfts` | `tau_jmfts` | a JMFTS-backed session store |
|
|
189
|
+
|
|
190
|
+
MIT © Fight Fire with Fire Robotics, LLC
|