# TAKT Architecture Knowledge

## Security Premises

TAKT lets AI agents read repository, PR, issue, and other content with the user's permissions and use tools allowed by the provider configuration. The user specifies the command to run and the conditions that select its targets (such as a PR number or filters). `takt --pr`, caccia, and review workflows operate on this premise. Tool restrictions in the provider configuration are not OS-level isolation.

TAKT does not defend in code against AI agents following instructions that the creators of PRs, repositories, or other content have embedded in that content (prompt injection). Paths outside this premise remain within the scope of protection. Examples include paths where Git configuration or attributes run external commands without an AI agent, paths that start on third-party content without user action, and paths that pass credentials to the content's author.

## Synchronizing Project Configuration into Worktrees

Some TAKT worktree creation paths synchronize project-local settings and runtime assets from `.takt/`; reuse also synchronizes them under certain conditions. The synchronized resources are `config.yaml`, `workflows`, `facets`, `steps`, and `quality-gates`, excluding generated `quality-gates/logs`. If synchronized content differs from the checked-out branch, it can appear as a worktree diff before the task makes any edits.

## Core Structure

WorkflowEngine is a state machine. It manages step transitions via EventEmitter.

```
CLI → WorkflowEngine → Runner (4 types) → RuleEvaluator → next step
```

| Runner | Purpose | When to Use |
|--------|---------|-------------|
| StepExecutor | Standard 3-phase execution | Default |
| ParallelRunner | Concurrent sub-steps | parallel block |
| ArpeggioRunner | Data-driven batch processing | arpeggio block |
| TeamLeaderRunner | Task decomposition → parallel sub-agents | team_leader block |

Runners are mutually exclusive. Do not specify multiple runner types on a single step.

### 3-Phase Execution Model

Normal steps execute in up to 3 phases. Sessions persist across phases.

| Phase | Purpose | Tools | Condition |
|-------|---------|-------|-----------|
| Phase 1 | Main work | Step's allowed_tools | Always |
| Phase 2 | Report output | Read-only tools | When output_contracts defined |
| Phase 3 | Status judgment | None (judgment only) | When tag-based rules exist |

## Rule Evaluation

RuleEvaluator evaluates every rule in YAML order and adopts the first matching rule. Semantic labels are selected once during Phase 3; `when(...)` and aggregate conditions are evaluated deterministically in that same ordered loop. If no rule matches, the workflow aborts with `rule_no_match`.

| Priority | Method | Target |
|----------|--------|--------|
| YAML order | condition | first true rule |

### Condition Syntax

| Syntax | Parsing | Regex |
|--------|---------|-------|
| `when(...)` | Deterministic workflow-state predicate | `isWhenConditionExpression` |
| `all("...")` / `any("...")` | Aggregate condition | `AGGREGATE_CONDITION_REGEX` |
| Plain semantic label | Matches the single Phase 3 selection | — |

Semantic and aggregate conditions can be combined with `&& when(...)`. The condition parser and RuleEvaluator must be updated together for new syntax.

## Provider Integration

Abstracted through the Provider interface. SDK-specific details are encapsulated within each provider.

```
Provider.setup(AgentSetup) → ProviderAgent
ProviderAgent.call(prompt, options) → AgentResponse
```


### Model Resolution

Provider selection follows the priority order below. For the model, TAKT considers only the first entry in that order that specifies one. If the same entry specifies a provider, TAKT uses the model only when that provider name exactly matches the selected provider; otherwise, it leaves the model unset, uses the selected provider's default model, and stops without checking lower-priority model entries. A model without a provider is passed through unchanged. Provider names are compared as strings, so `claude` and `claude-sdk` are different. TAKT does not validate model compatibility.

1. CLI / environment explicit override
2. Matching promotion (normal agent steps only; parallel sub-steps disallow `promotion` at the schema level)
3. Step / parallel sub-step direct provider / model
4. workflow_call override
5. provider_routing (steps → tags → personas)
6. persona_providers (deprecated)
7. Auto routing
8. Workflow → project config.yaml → global config.yaml → provider default

## Auxiliary Entry Contracts

In TAKT, workflow runtime is not the only user-visible contract entry. Preview, doctor, workflow summary, validation, and report paths are also contract entries. Auxiliary entries that display or validate config values, providers, models, tools, permissions, or output contracts should use the same normalized input, resolver, and override order as runtime.


## Runtime Asset Consumption Boundaries

TAKT runtime assets get their meaning from the entry point that consumes them, not only from their location or name. The same string can be an asset reference, session identifier, display name, or directly supplied body, and each is a separate contract.


### Reference Names and Identity Names

Strings such as `persona`, `session_key`, and `name` mean different things depending on whether they are reference names or identity names. A reference name causes the corresponding resolver to load an asset. An identity name is a key for sessions, logs, state, or display, and a same-named file is not used unless that entry reads it. When adding a new asset, trace the loader that reads it and the call site that consumes it.

## Facet Assembly

The faceted-prompting module is independent from TAKT core.

```
compose(facets, options) → ComposedPrompt { systemPrompt, userMessage }
```


### 3-Layer Facet Resolution Priority

Project `.takt/` → User `~/.takt/` → Builtin `builtins/{lang}/`

Same-named facets are overridden by higher-priority layers. Customize builtins by overriding in upper layers.

## Test Layers and Execution Gates

TAKT classifies unit, light integration, heavy integration, and E2E tests by the boundaries they actually cross, not by filename or duration. A test that starts a real child process is still heavy integration rather than E2E when it calls a local fake CLI from an internal client instead of entering through a user-facing command.

| Layer | Boundary | Standard Gate |
|-------|----------|---------------|
| Unit | Individual function or class; direct dependencies are test doubles, with no real process, Git, filesystem, or workflow engine | `npm test` |
| Light integration | Real filesystem, bounded storage, or multiple production components, without resource-heavy process or engine execution | `npm run test:it` |
| Heavy integration | Real child process, Git, complete WorkflowEngine or TeamLeader execution, or a measured resource-heavy case requiring serial execution | `npm run test:it:heavy` |
| E2E | Runs the application from a user-facing entry point such as the CLI and observes user-visible results | Provider-specific E2E gate |

### Development Execution Order

| State | Execution |
|-------|-----------|
| During implementation | Repeat the unit gate |
| After implementation | Run the light integration gate after the unit gate |
| Added or changed an integration test | Run the `releaseVerificationWiring.test.ts` classification contract by itself |
| Added or changed a heavy integration test | Run the changed file yourself as a target instead of waiting for the full heavy suite |
| Pull request or release | Run the complete light and heavy integration suites |

Heavy integration runners use one worker to avoid process, Git, and synchronous I/O contention. Full local execution is serial, while pull-request CI splits heavy parallel integration across six isolated runners and isolates each serial group on its own runner. `npm test -- <test-file>` routes a classified target to the corresponding runner. The owner of a new or changed heavy integration test must leave this targeted run as completion evidence and must not delegate its first execution to the pull-request-wide heavy gate. `npm run check:release` runs unit, light integration, heavy integration, and E2E in order.

### Mock Provider

`--provider mock` returns deterministic responses. Scenario queues compose multi-turn tests.

```typescript
// Avoid: Calling real API in tests
const response = await callClaude(prompt)

// Example: Set up scenario with mock provider
setMockScenario([
  { persona: 'coder', status: 'done', content: '[STEP:1]\nDone.' },
  { persona: 'reviewer', status: 'done', content: '[STEP:1]\napproved' },
])
```

### Test Isolation

Shared test setup assigns an isolated configuration root to `TAKT_CONFIG_DIR` for each test environment. Except for tests of configuration-directory selection itself, tests create required configuration inside the assigned root rather than replacing the process-wide `TAKT_CONFIG_DIR`. Replacing a process-wide environment variable can leak state into parallel or subsequent tests.


## Platform Priority

TAKT treats Windows as a secondary platform. macOS and Linux are the primary development and verification environments; unless the requirement or pre-change user-facing support contract requires confirmation on Windows, such confirmation is not a completion criterion for tasks. The Windows CI job runs some tests on Windows; it does not promise Windows support for every feature.

## Error Propagation

Provider errors propagate through: `AgentResponse.error` → session log → console output.


## Session Management

Agent sessions are stored per-cwd and per-provider. In a worktree clone, steps with `session: continue` and no provider fallback resume a saved session for the same persona, provider, and model within the same run, without using sessions from another clone or the project root. A new attempt through retry, requeue, or instruct starts each persona with a fresh session in the existing clone; later steps resume that session.

When a normal Phase 1 response merely omits `sessionId`, that alone is not a reason to discard the existing session. Paths that are allowed to continue the existing resume context should preserve the old sessionId.

However, when a retry or fallback explicitly runs as a new session and succeeds, a missing `sessionId` must not continue using the old resumed session. The storage layer must be told that the new run produced no sessionId, so the old session is cleared or isolated.

The Report Phase is Phase 2: it uses read-only tools to read necessary parts of the Phase 1 execution record and related files, then summarizes the results. It does not modify files, run tests or builds, or add new investigations, findings, judgments, or plans; TAKT saves the response body. Report retry/fallback preserves `permissionMode: readonly`, read-only tool restrictions, and provider capability overrides such as turn limits. Phase 3 uses no tools.


## Asynchronous Work and Process Boundaries

Work that must complete after the calling CLI exits cannot be owned only by an unresolved Promise in the CLI process. Before the CLI returns, ownership must transfer to a worker or external service that can survive independently of the CLI process.

| Boundary | Fact to Verify |
|----------|----------------|
| Worker launch target | Resolves from both the distributed build and every supported source or development execution mode |
| Child-process start | Spawn success is observed separately from module loading, work completion, and exit status |
| Parent CLI exit | Required artifacts appear after parent exit, or failure remains observable |
| Polling | A time or attempt bound and the result at that bound are defined |
| Marker or artifact publication | Transient absence or read races during replacement follow the publication contract through bounded retry or explicit failure |

A launch API returning without an error does not prove that the worker loaded its target module or completed its work. Use an artifact or persisted failure whose lifetime does not depend on the parent process. An exit status is independent evidence only when an independent worker or supervisor that survives the parent records it durably.


## Termination-Path Completeness

The lifetime of a temporary file or external resource depends on ownership from creation through the last consumer and on reachable terminal paths. When a change concerns that lifetime, the entries and terminals it reaches form the affected paths. Whether normal completion, failure, cancellation, and forced termination apply differs by path. `process.exit()` and forced termination (repeated SIGINT or an abort handler that exits immediately) do not run `finally` blocks, so cleanup relying on `finally` is bypassed on those paths.
