Skip to content

Commit 76d0a84

Browse files
authored
Merge pull request #101 from gradion-ai/wip-rewrite
Rewrite freeact on a new modular architecture
2 parents 37f86ad + 2c3805f commit 76d0a84

204 files changed

Lines changed: 12447 additions & 13157 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

AGENTS.md

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
# Repository Guidelines
22

3+
## Architecture in Four Sentences
4+
Freeact is an agent SDK plus a terminal UI joined only by an event stream: `Agent.stream()` yields typed `AgentEvent`s (including `ApprovalRequest`s the consumer must resolve), and the terminal renders them. The agent is policy-free; permission rules and approval decisions belong to the embedder (`cli.py` wires agent, permissions, and TUI together). Configuration flows one way: `.freeact/config.toml` -> `config.load()` -> `config.resolve()` -> `ResolvedRuntime` -> `Agent`. Code actions run in an ipybox kernel; bundled tools run as MCP subprocess servers under `freeact/tools/`.
5+
36
## Project Structure & Module Organization
47
- Documentation: `docs/`
58
- Project description: `docs/index.md`
69
- Source modules:
7-
- `freeact/agent/`: core agent, config, session store, shell command extraction, media processing
8-
- `freeact/tools/`: tool definitions, Python tool generation, tool search
9-
- `freeact/terminal/`: terminal UI (app, widgets, screens, clipboard, completion)
10-
- `freeact/permissions.py`: permission management
10+
- `freeact/events.py`, `freeact/toolcalls.py`: SDK event and tool call types
11+
- `freeact/config/`: TOML config (schema, load/init, resolve, prompts, skills)
12+
- `freeact/agent/`: agent runtime (turn loop, approvals, executor, MCP, session, subagents)
13+
- `freeact/tools/`: bundled MCP tool servers (filesystem, fetch, search, discovery)
14+
- `freeact/terminal/`: terminal UI (app, dispatcher, view, approvals, widgets, screens, clipboard)
15+
- `freeact/permissions.py`: permission rules and manager
1116
- `freeact/cli.py`: CLI entry point
1217
- Tests:
1318
- `tests/unit/`: unit tests
@@ -17,10 +22,9 @@
1722
- `docs/AGENTS.md`: documentation authoring
1823
- `tests/AGENTS.md`: testing conventions and utilities
1924

20-
## Architecture Constraints
21-
- `docs/internal/architecture/README.md`: index of constraint files and runtime docs
22-
- Load only the constraint file relevant to your current task
23-
- Flag when a change may require updating or extending a constraint file
25+
## Invariants
26+
- `docs/internal/invariants.md`: non-obvious rules and why-invariants; read before changing core behavior
27+
- Flag when a change may require updating an invariant
2428

2529
## Coding Guidelines
2630
- All function parameters and return types must have type hints

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@ For programmatic MCP tool calling ("code mode"), freeact generates typed Python
2323
Freeact supports tool discovery via agentic and semantic search, loading only task-relevant tool information into the context window. It can enforce application-level approval of code actions, shell commands, and programmatic tool calls, originating from both main agents and subagents. Freeact runs locally on your computer and is available as a CLI tool and Python SDK.
2424

2525
> [!NOTE]
26-
> **Supported models**: Freeact supports any model compatible with [Pydantic AI](https://ai.pydantic.dev/). See [Models](https://gradion-ai.github.io/freeact/models/) for provider configuration and examples.
26+
> **Supported models**: Freeact supports any model compatible with [Pydantic AI](https://ai.pydantic.dev/). See [Configure models](https://gradion-ai.github.io/freeact/guides/models/) for provider configuration and examples.
2727
2828
## Documentation
2929

3030
- 📚 [Documentation](https://gradion-ai.github.io/freeact/)
31-
- 🚀 [Quickstart](https://gradion-ai.github.io/freeact/quickstart/)
31+
- 🚀 [Quickstart](https://gradion-ai.github.io/freeact/getting-started/quickstart/)
3232
- 🤖 [llms.txt](https://gradion-ai.github.io/freeact/llms.txt)
3333
- 🤖 [llms-full.txt](https://gradion-ai.github.io/freeact/llms-full.txt)
3434

@@ -51,8 +51,8 @@ Freeact supports tool discovery via agentic and semantic search, loading only ta
5151

5252
| Component | Description |
5353
|---|---|
54-
| **[Agent SDK](https://gradion-ai.github.io/freeact/sdk/)** | Agent harness and Python API for building freeact applications. |
55-
| **[CLI tool](https://gradion-ai.github.io/freeact/cli/)** | Terminal interface for interactive conversations with a freeact agent. |
54+
| **[Agent SDK](https://gradion-ai.github.io/freeact/getting-started/sdk-tutorial/)** | Agent harness and Python API for building freeact applications. |
55+
| **[CLI tool](https://gradion-ai.github.io/freeact/getting-started/quickstart/)** | Terminal interface for interactive conversations with a freeact agent. |
5656

5757
---
5858

docs/api/agent.md

Lines changed: 30 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,47 @@
1-
::: freeact.agent.Agent
2-
options:
3-
filters:
4-
- "!^tool_names$"
5-
- "!^_await_approval_or_cancel$"
1+
::: freeact.Agent
62

7-
::: freeact.agent.AgentEvent
3+
::: freeact.AgentEvent
84

9-
::: freeact.agent.Response
5+
::: freeact.Response
106

11-
::: freeact.agent.ResponseChunk
7+
::: freeact.ResponseChunk
128

13-
::: freeact.agent.Thoughts
9+
::: freeact.Thoughts
1410

15-
::: freeact.agent.ThoughtsChunk
11+
::: freeact.ThoughtsChunk
1612

17-
::: freeact.agent.CodeExecutionOutput
18-
options:
19-
filters:
20-
- "!^format$"
21-
- "!^approval_rejected$"
13+
::: freeact.CodeExecutionOutput
2214

23-
::: freeact.agent.CodeExecutionOutputChunk
15+
::: freeact.CodeExecutionOutputChunk
2416

25-
::: freeact.agent.ApprovalRequest
17+
::: freeact.ApprovalRequest
2618

27-
::: freeact.agent.ToolOutput
19+
::: freeact.ToolOutput
2820

29-
::: freeact.agent.Cancelled
21+
::: freeact.Cancelled
3022

31-
::: freeact.agent.ToolCall
23+
::: freeact.Phase
3224

33-
::: freeact.agent.GenericCall
25+
::: freeact.CancelToken
3426

35-
::: freeact.agent.ShellAction
27+
::: freeact.ToolCall
3628

37-
::: freeact.agent.CodeAction
29+
::: freeact.GenericCall
3830

39-
::: freeact.agent.FileRead
31+
::: freeact.ShellAction
4032

41-
::: freeact.agent.FileWrite
33+
::: freeact.CodeAction
4234

43-
::: freeact.agent.FileEdit
35+
::: freeact.FileRead
36+
37+
::: freeact.FileWrite
38+
39+
::: freeact.FileEdit
40+
41+
::: freeact.suggest_pattern
42+
43+
::: freeact.suggest_display
44+
45+
::: freeact.parse_pattern
46+
47+
::: freeact.extract_tool_output_text

docs/api/config.md

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,35 @@
1-
::: freeact.config.PersistentConfig
1+
::: freeact.config.load
22

3-
::: freeact.agent.config.Config
3+
::: freeact.config.init
44

5-
::: freeact.agent.config.SkillMetadata
5+
::: freeact.config.resolve
66

7-
::: freeact.agent.config.DEFAULT_MODEL_NAME
7+
::: freeact.config.workspace
88

9-
::: freeact.agent.config.DEFAULT_MODEL_SETTINGS
9+
::: freeact.config.FreeactConfig
1010

11-
::: freeact.agent.config.BASIC_SEARCH_MCP_SERVER_CONFIG
11+
::: freeact.config.AgentSection
1212

13-
::: freeact.agent.config.HYBRID_SEARCH_MCP_SERVER_CONFIG
13+
::: freeact.config.ToolPresets
1414

15-
::: freeact.agent.config.GOOGLE_SEARCH_MCP_SERVER_CONFIG
15+
::: freeact.config.TerminalSection
1616

17-
::: freeact.agent.config.FILESYSTEM_MCP_SERVER_CONFIG
17+
::: freeact.config.ResolvedRuntime
1818

19-
::: freeact.agent.config.FETCH_MCP_SERVER_CONFIG
19+
::: freeact.config.Workspace
20+
21+
::: freeact.config.SkillMetadata
22+
23+
::: freeact.config.DEFAULT_MODEL_NAME
24+
25+
::: freeact.config.DEFAULT_MODEL_SETTINGS
26+
27+
::: freeact.config.FILESYSTEM_MCP_SERVER_CONFIG
28+
29+
::: freeact.config.BASIC_SEARCH_MCP_SERVER_CONFIG
30+
31+
::: freeact.config.HYBRID_SEARCH_MCP_SERVER_CONFIG
32+
33+
::: freeact.config.GOOGLE_SEARCH_MCP_SERVER_CONFIG
34+
35+
::: freeact.config.FETCH_MCP_SERVER_CONFIG

docs/concepts/approvals.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Approvals and permissions
2+
3+
Every code action, shell command, and tool call requires approval before execution, unless a permission rule pre-approves it. This page explains the approval model: what gets approved, who decides, and why permission matching works the way it does.
4+
5+
## The approval model
6+
7+
A code action is approved as a whole before it runs. Shell commands and programmatic tool calls inside it are then intercepted during execution and approved individually. This two-level model means a single approved code action does not grant blanket permission to everything it does: each side effect surfaces as its own decision point.
8+
9+
Interception happens with full runtime information. Python variables in shell commands are resolved before the approval request, so the user approves the actual command values, not templates. Composite shell commands joined with `&&`, `||`, `|`, or `;` are decomposed into individual sub-commands, each approved separately, so a harmless prefix cannot smuggle in a destructive suffix. `%%bash` scripts are approved as a whole.
10+
11+
The `tool_call` type of an [`ApprovalRequest`][freeact.ApprovalRequest] determines what is being approved:
12+
13+
| `tool_call` type | Trigger |
14+
|---|---|
15+
| [`CodeAction`][freeact.CodeAction] | Code action containing Python code and shell commands to execute |
16+
| [`ShellAction`][freeact.ShellAction] | Shell command (`!cmd`) or shell script (`%%bash`) intercepted during code action execution |
17+
| [`GenericCall`][freeact.GenericCall] | Programmatic tool call (intercepted during code action execution) or JSON tool call |
18+
| [`FileRead`][freeact.FileRead], [`FileWrite`][freeact.FileWrite], [`FileEdit`][freeact.FileEdit] | Filesystem operation via built-in MCP server |
19+
20+
`GenericCall` covers both call styles; its `ptc` field is `True` for programmatic tool calls, so applications can match `GenericCall(ptc=True)` to treat them differently from JSON tool calls.
21+
22+
## Who decides
23+
24+
The agent itself is policy-free. It yields [`ApprovalRequest`][freeact.ApprovalRequest] events from its [event stream](runtime.md#events) and suspends execution until the application calls `approve()`. Calling `approve(True)` executes the action; `approve(False)` rejects it and ends the current agent turn. A rejection is signaled structurally: the final [`CodeExecutionOutput`][freeact.CodeExecutionOutput] has `approval_rejected=True`.
25+
26+
The embedding application decides how requests are resolved. The CLI prompts the user interactively. The [SDK tutorial](../getting-started/sdk-tutorial.md) auto-approves everything. Applications that want stored rules use [`PermissionManager`][freeact.PermissionManager], the same utility the CLI uses internally; the agent never reads `permissions.toml` itself. See [Manage permissions](../guides/permissions.md) for both the interactive and the programmatic workflow.
27+
28+
Two safeguards prevent approval requests from hanging forever: if the consumer abandons the stream while an approval is pending, the request is resolved as rejected, and with a configured `approval_timeout`, an unresolved request is rejected when the timeout expires.
29+
30+
Subagent actions go through the same mechanism. Events from subagents flow through the parent agent's stream, with `agent_id` identifying the source, so the application applies one approval policy to the whole agent tree.
31+
32+
## Why two tiers
33+
34+
Permission rules are organized into `allow` and `ask` tiers, evaluated **ask then allow**: if a rule matches both tiers, the user is prompted. This order makes `ask` rules a safety net. A broad convenience rule like `allow git *` cannot silently cover a pattern the user wants to always confirm, such as `ask rm *`. Each tier supports an **always** scope (persisted to `permissions.toml`) and a **session** scope (in-memory, cleared when the session ends), separating durable policy from one-off trust.
35+
36+
## The path matching model
37+
38+
Rules for filesystem operations match on paths, and the matching is designed around the working directory as the trust boundary. Paths are normalized before matching: absolute paths under the working directory become relative, paths outside stay absolute. A relative pattern like `src/**` can therefore only ever match files inside the workspace, regardless of how the agent spells the path. Granting access outside the workspace requires an explicit absolute pattern with a leading `/`. Within patterns, `*` stays inside a single directory while `**` crosses directory boundaries, so a rule scoped to one directory does not silently extend to its subtree. See [Permission rules](../reference/permission-rules.md) for the exact syntax.
Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
# Code Execution
1+
# Code actions
22

3-
Freeact executes Python code, shell commands, and programmatic MCP tool calls in an IPython kernel provided by [ipybox](https://github.com/gradion-ai/ipybox). A [unified execution interface](https://gradion-ai.github.io/ipybox/codeexec/) enables their combination within a single code action. Code actions, shell commands, and programmatic tool calls are subject to [action approval](#action-approval) before execution.
3+
Freeact agents act by executing code rather than emitting one JSON tool call per LLM inference. A code action can contain any combination of Python code, shell commands, and programmatic MCP tool calls, generated in one LLM inference pass. This enables composition of tool calls, intermediate result processing, and control flow within a single action, reducing LLM roundtrips. For programmatic MCP tool calling ("code mode"[^1]), freeact generates typed Python APIs from MCP server schemas; the agent inspects generated APIs prior to execution and composes them based on available type information.
4+
5+
Code actions execute in an IPython kernel provided by [ipybox](https://github.com/gradion-ai/ipybox). A [unified execution interface](https://gradion-ai.github.io/ipybox/codeexec/) enables their combination within a single code action. Code actions, shell commands, and programmatic tool calls are subject to [approval](approvals.md) before execution.
46

57
## Python Code
68

@@ -38,13 +40,9 @@ ls -la
3840

3941
Shell state (working directory, variables) does not persist across `!` lines but persists within a `%%bash` block. Neither carries state to the next cell execution. Their results can be stored in variables though.
4042

41-
!!! note "`%%bash` approval"
42-
43-
Approval support for `%%bash` cell magic is not implemented yet (coming soon).
44-
4543
## Programmatic Tool Calls
4644

47-
[Generated Python APIs](sdk.md#generation-api) for MCP server tools can be imported and called like regular packages:
45+
[Generated Python APIs](../guides/tool-servers.md#add-servers-for-programmatic-tool-calling) for MCP server tools can be imported and called like regular packages:
4846

4947
```python
5048
from mcptools.google.web_search import run, Params
@@ -83,12 +81,10 @@ filename = "report.pdf"
8381

8482
## Action Approval
8583

86-
Code actions, contained shell commands, and programmatic tool calls require approval before execution. Shell commands and programmatic tool calls are intercepted during code action execution for individual approval.
87-
88-
Composite shell commands (using `&&`, `||`, `|`, `;`) are decomposed into individual sub-commands, each approved separately. Python variables in shell commands are resolved before the approval request, so the approval request shows actual values.
89-
90-
See the Agent SDK for programmatic [approval](sdk.md#approval) control, [permission configuration](configuration.md#permissions) for action pre-approval, and the CLI tool for the interactive [approval prompt](cli.md#approval-prompt).
84+
Code actions, contained shell commands, and programmatic tool calls require approval before execution. Shell commands and programmatic tool calls are intercepted during code action execution for individual approval. [Approvals and permissions](approvals.md) explains the approval model; [Manage permissions](../guides/permissions.md) covers pre-approval with permission rules.
9185

9286
## Working Directory
9387

9488
The kernel starts in the agent's workspace directory. After each code action, the working directory is reset to this location. If code changes the directory via `os.chdir()` or `%cd`, the change is undone before the next execution and the kernel prints a `[ipybox] cwd reset to <path>` message.
89+
90+
[^1]: [Code Mode: the better way to use MCP](https://blog.cloudflare.com/code-mode/)

0 commit comments

Comments
 (0)